summaryrefslogtreecommitdiff
path: root/src/main/java/ftbsc
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/ftbsc')
-rw-r--r--src/main/java/ftbsc/lll/exceptions/MappingNotFoundException.java4
-rw-r--r--src/main/java/ftbsc/lll/processor/LilleroProcessor.java80
-rw-r--r--src/main/java/ftbsc/lll/processor/tools/SrgMapper.java211
-rw-r--r--src/main/java/ftbsc/lll/processor/tools/obfuscation/ObfuscationMapper.java167
4 files changed, 218 insertions, 244 deletions
diff --git a/src/main/java/ftbsc/lll/exceptions/MappingNotFoundException.java b/src/main/java/ftbsc/lll/exceptions/MappingNotFoundException.java
index 817761b..a1a47d1 100644
--- a/src/main/java/ftbsc/lll/exceptions/MappingNotFoundException.java
+++ b/src/main/java/ftbsc/lll/exceptions/MappingNotFoundException.java
@@ -1,9 +1,9 @@
package ftbsc.lll.exceptions;
-import ftbsc.lll.processor.tools.SrgMapper;
+import ftbsc.lll.processor.tools.obfuscation.ObfuscationMapper;
/**
- * Thrown upon failure to find the requested mapping within a loaded {@link SrgMapper}.
+ * Thrown upon failure to find the requested mapping within a loaded {@link ObfuscationMapper}.
*/
public class MappingNotFoundException extends RuntimeException {
diff --git a/src/main/java/ftbsc/lll/processor/LilleroProcessor.java b/src/main/java/ftbsc/lll/processor/LilleroProcessor.java
index 7d2645e..c6343a6 100644
--- a/src/main/java/ftbsc/lll/processor/LilleroProcessor.java
+++ b/src/main/java/ftbsc/lll/processor/LilleroProcessor.java
@@ -6,7 +6,7 @@ import ftbsc.lll.exceptions.AmbiguousDefinitionException;
import ftbsc.lll.exceptions.MappingNotFoundException;
import ftbsc.lll.exceptions.TargetNotFoundException;
import ftbsc.lll.processor.annotations.*;
-import ftbsc.lll.processor.tools.SrgMapper;
+import ftbsc.lll.processor.tools.obfuscation.ObfuscationMapper;
import ftbsc.lll.proxies.FieldProxy;
import ftbsc.lll.proxies.MethodProxy;
@@ -33,6 +33,7 @@ import static ftbsc.lll.processor.tools.ASTUtils.*;
*/
@SupportedAnnotationTypes("ftbsc.lll.processor.annotations.Patch")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
+@SupportedOptions("mappingsFile")
public class LilleroProcessor extends AbstractProcessor {
/**
* A {@link Set} of {@link String}s that will contain the fully qualified names
@@ -41,9 +42,35 @@ public class LilleroProcessor extends AbstractProcessor {
private final Set<String> generatedInjectors = new HashSet<>();
/**
- * A static boolean that should be set to true when ran in a non-obfuscated environment.
+ * The {@link ObfuscationMapper} used to convert classes and variables
+ * to their obfuscated equivalent. Will be null when no mapper is in use.
*/
- public static boolean obfuscatedEnvironment = false; //todo: set this
+ private ObfuscationMapper mapper;
+
+ /**
+ * Initializes the processor with the processing environment by
+ * setting the {@code processingEnv} field to the value of the
+ * {@code processingEnv} argument.
+ * @param processingEnv environment to access facilities the tool framework
+ * provides to the processor
+ * @throws IllegalStateException if this method is called more than once.
+ */
+ @Override
+ public synchronized void init(ProcessingEnvironment processingEnv) {
+ super.init(processingEnv);
+ String location = processingEnv.getOptions().get("mappingsFile");
+ if(location == null)
+ mapper = null;
+ else { //TODO: add local file
+ try {
+ URL url = new URL(location);
+ InputStream is = url.openStream();
+ mapper = new ObfuscationMapper(new BufferedReader(new InputStreamReader(is,
+ StandardCharsets.UTF_8)).lines());
+ is.close();
+ } catch(IOException ignored) {} //TODO: proper handling
+ }
+ }
/**
* Where the actual processing happens.
@@ -106,7 +133,7 @@ public class LilleroProcessor extends AbstractProcessor {
/**
* Finds the class name and maps it to the correct format.
* @param name the fully qualified name of the class to convert
- * @param mapper the {@link SrgMapper} to use, may be null
+ * @param mapper the {@link ObfuscationMapper} to use, may be null
* @implNote De facto, there is never any difference between the SRG and MCP name of a class.
* In theory, differences only arise between SRG/MCP names and Notch (fully obfuscated)
* names. However, this method still performs a conversion - just in case there is an
@@ -114,15 +141,15 @@ public class LilleroProcessor extends AbstractProcessor {
* @return the fully qualified class name
* @since 0.3.0
*/
- private static String findClassName(String name, SrgMapper mapper) {
- return mapper == null ? name : mapper.mapClass(name, obfuscatedEnvironment).replace('/', '.');
+ private static String findClassName(String name, ObfuscationMapper mapper) {
+ return mapper == null ? name : mapper.obfuscateClass(name).replace('/', '.');
}
/**
* Finds the class name and maps it to the correct format.
* @param patchAnn the {@link Patch} annotation containing target class info
* @param methodAnn the {@link FindMethod} annotation to fall back on, may be null
- * @param mapper the {@link SrgMapper} to use, may be null
+ * @param mapper the {@link ObfuscationMapper} to use, may be null
* @implNote De facto, there is never any difference between the SRG and MCP name of a class.
* In theory, differences only arise between SRG/MCP names and Notch (fully obfuscated)
* names. However, this method still performs a conversion - just in case there is an
@@ -130,7 +157,7 @@ public class LilleroProcessor extends AbstractProcessor {
* @return the fully qualified class name
* @since 0.3.0
*/
- private static String findClassName(Patch patchAnn, FindMethod methodAnn, SrgMapper mapper) {
+ private static String findClassName(Patch patchAnn, FindMethod methodAnn, ObfuscationMapper mapper) {
String fullyQualifiedName =
methodAnn == null || methodAnn.parent() == Object.class
? getClassFullyQualifiedName(patchAnn.value())
@@ -141,11 +168,11 @@ public class LilleroProcessor extends AbstractProcessor {
/**
* Finds the class name and maps it to the correct format.
* @param patchAnn the {@link Patch} annotation containing target class info
- * @param mapper the {@link SrgMapper} to use, may be null
+ * @param mapper the {@link ObfuscationMapper} to use, may be null
* @return the internal class name
* @since 0.3.0
*/
- private static String findClassName(Patch patchAnn, SrgMapper mapper) {
+ private static String findClassName(Patch patchAnn, ObfuscationMapper mapper) {
return findClassName(patchAnn, null, mapper);
}
@@ -153,12 +180,12 @@ public class LilleroProcessor extends AbstractProcessor {
* Finds the member name and maps it to the correct format.
* @param parentFQN the already mapped FQN of the parent class
* @param memberName the name of the member
- * @param mapper the {@link SrgMapper} to use, may be null
+ * @param mapper the {@link ObfuscationMapper} to use, may be null
* @return the internal class name
* @since 0.3.0
*/
- private static String findMemberName(String parentFQN, String memberName, SrgMapper mapper) {
- return mapper == null ? memberName : mapper.mapMember(parentFQN, memberName, obfuscatedEnvironment);
+ private static String findMemberName(String parentFQN, String memberName, ObfuscationMapper mapper) {
+ return mapper == null ? memberName : mapper.obfuscateMember(parentFQN, memberName);
}
/**
@@ -166,11 +193,11 @@ public class LilleroProcessor extends AbstractProcessor {
* @param parentFQN the already mapped FQN of the parent class
* @param methodAnn the {@link FindMethod} annotation to fall back on, may be null
* @param stub the {@link ExecutableElement} for the stub
- * @param mapper the {@link SrgMapper} to use, may be null
+ * @param mapper the {@link ObfuscationMapper} to use, may be null
* @return the internal class name
* @since 0.3.0
*/
- private static String findMethodName(String parentFQN, FindMethod methodAnn, ExecutableElement stub, SrgMapper mapper) {
+ private static String findMethodName(String parentFQN, FindMethod methodAnn, ExecutableElement stub, ObfuscationMapper mapper) {
String methodName = methodAnn == null ? stub.getSimpleName().toString() : methodAnn.name();
try {
methodName = findMemberName(parentFQN, methodName, mapper);
@@ -188,11 +215,11 @@ public class LilleroProcessor extends AbstractProcessor {
* @param patchAnn the {@link Patch} annotation containing target class info
* @param methodAnn the {@link FindMethod} annotation to fall back on, may be null
* @param stub the {@link ExecutableElement} for the stub
- * @param mapper the {@link SrgMapper} to use
+ * @param mapper the {@link ObfuscationMapper} to use
* @return the internal class name
* @since 0.3.0
*/
- private static String findMethodName(Patch patchAnn, FindMethod methodAnn, ExecutableElement stub, SrgMapper mapper) {
+ private static String findMethodName(Patch patchAnn, FindMethod methodAnn, ExecutableElement stub, ObfuscationMapper mapper) {
return findMethodName(findClassName(patchAnn, methodAnn, mapper), methodAnn, stub, mapper);
}
@@ -250,13 +277,13 @@ public class LilleroProcessor extends AbstractProcessor {
/**
* Finds the real method corresponding to a stub.
* @param stub the {@link ExecutableElement} for the stub
- * @param mapper the {@link SrgMapper} to use
+ * @param mapper the {@link ObfuscationMapper} to use
* @return the desired method, if it exists
* @throws AmbiguousDefinitionException if it finds more than one candidate
* @throws TargetNotFoundException if it finds no valid candidate
* @since 0.3.0
*/
- private ExecutableElement findRealMethod(ExecutableElement stub, SrgMapper mapper) {
+ private ExecutableElement findRealMethod(ExecutableElement stub, ObfuscationMapper mapper) {
Patch patchAnn = stub.getEnclosingElement().getAnnotation(Patch.class);
FindMethod findAnn = stub.getAnnotation(FindMethod.class); //this may be null, it means no fallback info
Target target = stub.getAnnotation(Target.class); //if this is null strict mode is always disabled
@@ -272,12 +299,12 @@ public class LilleroProcessor extends AbstractProcessor {
/**
* Finds the real field corresponding to a stub.
* @param stub the {@link ExecutableElement} for the stub
- * @param mapper the {@link SrgMapper} to use
+ * @param mapper the {@link ObfuscationMapper} to use
* @return the desired method, if it exists
* @throws TargetNotFoundException if it finds no valid candidate
* @since 0.3.0
*/
- private VariableElement findField(ExecutableElement stub, SrgMapper mapper) {
+ private VariableElement findField(ExecutableElement stub, ObfuscationMapper mapper) {
Patch patchAnn = stub.getEnclosingElement().getAnnotation(Patch.class);
FindField fieldAnn = stub.getAnnotation(FindField.class);
String parentName = findClassName(getClassFullyQualifiedName(
@@ -307,15 +334,6 @@ public class LilleroProcessor extends AbstractProcessor {
* @param cl the {@link TypeElement} for the given class
*/
private void generateInjectors(TypeElement cl) {
- SrgMapper mapper = null;
- try { //TODO: cant we get it from local?
- URL url = new URL("https://data.fantabos.co/output.tsrg");
- InputStream is = url.openStream();
- mapper = new SrgMapper(new BufferedReader(new InputStreamReader(is,
- StandardCharsets.UTF_8)).lines());
- is.close();
- } catch(IOException ignored) {} //TODO: proper handling
-
//find class information
Patch patchAnn = cl.getAnnotation(Patch.class);
String targetClassSrgName = findClassName(patchAnn, mapper);
@@ -481,7 +499,7 @@ public class LilleroProcessor extends AbstractProcessor {
* @return a {@link List} of method specs
* @since 0.2.0
*/
- private List<MethodSpec> generateRequestedProxies(TypeElement cl, SrgMapper mapper) {
+ private List<MethodSpec> generateRequestedProxies(TypeElement cl, ObfuscationMapper mapper) {
List<MethodSpec> generated = new ArrayList<>();
findAnnotatedMethods(cl, FindMethod.class)
.stream()
diff --git a/src/main/java/ftbsc/lll/processor/tools/SrgMapper.java b/src/main/java/ftbsc/lll/processor/tools/SrgMapper.java
deleted file mode 100644
index d3c9f79..0000000
--- a/src/main/java/ftbsc/lll/processor/tools/SrgMapper.java
+++ /dev/null
@@ -1,211 +0,0 @@
-package ftbsc.lll.processor.tools;
-
-import ftbsc.lll.exceptions.MappingNotFoundException;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.stream.Stream;
-
-/**
- * Parses a .tsrg file into a mapper capable of converting from
- * deobfuscated names to SRG names.
- * Obviously, it may only be used at runtime if the .tsrg file is
- * included in the resources. However, in that case, I'd recommend
- * using the built-in Forge one and refrain from including an extra
- * resource for no good reason.
- * @since 0.2.0
- */
-public class SrgMapper {
-
- /**
- * A Map using the deobfuscated names as keys,
- * holding information for that Srg class as value.
- */
- private final Map<String, ObfuscationData> mapper = new HashMap<>();
-
- /**
- * The public constructor.
- * Should be passed a {@link Stream} of Strings, one representing each line.
- * Whether they contain line endings or not is irrelevant.
- * @param str a {@link Stream} of strings
- */
- public SrgMapper(Stream<String> str) {
- AtomicReference<String> currentClass = new AtomicReference<>("");
- str.forEach(l -> {
- if(l.startsWith("\t"))
- mapper.get(currentClass.get()).addMember(l);
- else {
- ObfuscationData s = new ObfuscationData(l);
- currentClass.set(s.mcpName);
- mapper.put(s.mcpName, s);
- }
- });
- }
-
- /**
- * Gets the SRG-obfuscated name of the class.
- * @param mcp the MCP (deobfuscated) internal name of the desired class
- * @return the SRG name of the class
- * @throws MappingNotFoundException if no mapping is found
- */
- public String getSrgClass(String mcp) {
- ObfuscationData data = mapper.get(mcp);
- if(data == null)
- throw new MappingNotFoundException(mcp);
- else return data.srgName;
- }
-
- /**
- * Gets the MCP (deobfuscated) name of the class.
- * Due to how it's implemented, it's considerably less efficient than its
- * opposite operation.
- * @param srg the SRG-obfuscated internal name of the desired class
- * @return the MCP name of the class
- */
- public String getMcpClass(String srg) {
- ObfuscationData data = getObfuscationData(srg);
- return data.mcpName;
- }
-
- /**
- * Gets one between the SRG and MCP names.
- * @param name the internal name of the desired class in either format
- * @param obf whether it should return the obfuscated name
- * @return a {@link String} containing the internal name of the class
- * @throws MappingNotFoundException if no mapping is found
- * @since 0.3.0
- */
- public String mapClass(String name, boolean obf) {
- String srg;
- try {
- srg = this.getSrgClass(name);
- } catch(MappingNotFoundException e) {
- srg = name;
- name = this.getMcpClass(srg);
- }
- if(obf) return srg;
- else return name;
- }
-
- /**
- * Gets the SRG-obfuscated name of a class member (field or method).
- * The method signature must be in this format: "methodName methodDescriptor",
- * with a space, because that's how it is in .tsrg files.
- * @param mcpClass the MCP (deobfuscated) internal name of the container class
- * @param member the field name or method signature
- * @return the SRG name of the given member
- * @throws MappingNotFoundException if no mapping is found
- */
- public String getSrgMember(String mcpClass, String member) {
- ObfuscationData data = mapper.get(mcpClass);
- if(data == null)
- throw new MappingNotFoundException(mcpClass + "::" + member);
- return data.members.get(member);
- }
-
- /**
- * Gets the MCP (deobfuscated) name of the given member.
- * Due to how it's implemented, it's considerably less efficient than its
- * opposite operation.
- * @param srgClass the SRG-obfuscated internal name of the container class
- * @param member the field name or method signature
- * @return the MCP name of the given member
- */
- public String getMcpMember(String srgClass, String member) {
- ObfuscationData data = getObfuscationData(srgClass);
- for(String mcp : data.members.keySet())
- if(data.members.get(mcp).equals(member))
- return mcp;
- return null;
- }
-
- /**
- * Obfuscates or deobfuscates a member, given one of its names and the effective.
- * @param className the internal or fully qualified name of the container class
- * @param memberName the member of the class
- * @param obf whether it should return the obfuscated name
- * @return the mapped member name
- * @throws MappingNotFoundException if no mapping is found
- * @since 0.3.0
- */
- public String mapMember(String className, String memberName, boolean obf) {
- className = className.replace('.', '/');
- String effectiveClassName = this.mapClass(className, obf);
- String srgMemberName;
- try {
- srgMemberName = this.getSrgMember(effectiveClassName, memberName);
- } catch(MappingNotFoundException e) {
- srgMemberName = memberName;
- memberName = this.getMcpMember(effectiveClassName, memberName);
- }
- if(obf) return srgMemberName;
- else return memberName;
- }
-
- /**
- * Used internally. Gets the obfuscation data corresponding to the given SRG name.
- * @return the desired {@link ObfuscationData} object
- * @throws MappingNotFoundException if no {@link ObfuscationData} object is found
- */
- private ObfuscationData getObfuscationData(String srg) {
- for(ObfuscationData s : mapper.values())
- if(s.srgName.equals(srg))
- return s;
- throw new MappingNotFoundException(srg);
- }
-
- /**
- * Private class used internally for storing information about each
- * class. It's private because there is no good reason anyone would
- * want to access this outside of this class.
- */
- private static class ObfuscationData {
- /**
- * The MCP internal name (FQN with '/' instad of '.') of the class.
- */
- private final String mcpName;
-
- /**
- * The SRG internal name (FQN with '/' instad of '.') of the class.
- */
- private final String srgName;
-
- /**
- * A {@link Map} tying each member's deobfuscated name or signature to its
- * SRG name.
- */
- private final Map<String, String> members;
-
-
- /**
- * The constructor. It takes in the line where the class is declared,
- * which looks something like this:
- * {@code internal/name/mcp internal/name/srg }
- * @param s the String represeting the declaration line
- */
- private ObfuscationData(String s) {
- String[] split = s.trim().split(" ");
- this.mcpName = split[0];
- this.srgName = split[1];
- this.members = new HashMap<>();
- }
-
- /**
- * Adds a member to the target class. It takes in the line where the
- * member is declared.
- * For fields it looks like this:
- * {@code fieldMcpName field_srg_name}
- * For methods it looks like this:
- * {@code methodName methodDescriptor method_srg_name}
- * @param s the String representing the declaration line
- */
- public void addMember(String s) {
- String[] split = s.trim().split(" ");
- if(split.length == 2) //field
- members.put(split[0], split[1]);
- else if (split.length == 3) //method
- members.put(split[0] + " " + split[1], split[2]);
- }
- }
-}
diff --git a/src/main/java/ftbsc/lll/processor/tools/obfuscation/ObfuscationMapper.java b/src/main/java/ftbsc/lll/processor/tools/obfuscation/ObfuscationMapper.java
new file mode 100644
index 0000000..1d19c6b
--- /dev/null
+++ b/src/main/java/ftbsc/lll/processor/tools/obfuscation/ObfuscationMapper.java
@@ -0,0 +1,167 @@
+package ftbsc.lll.processor.tools.obfuscation;
+
+import ftbsc.lll.exceptions.MappingNotFoundException;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Stream;
+
+/**
+ * Parses a .tsrg file into a mapper capable of converting from
+ * deobfuscated names to obfuscated ones.
+ * Obviously, it may only be used at runtime if the .tsrg file is
+ * included in the resources. However, in that case, I'd recommend
+ * using the built-in Forge one and refrain from including an extra
+ * resource for no good reason.
+ * TODO: CSV format
+ * @since 0.2.0
+ */
+public class ObfuscationMapper {
+
+ /**
+ * A Map using the deobfuscated names as keys,
+ * holding information for that class as value.
+ */
+ private final Map<String, ObfuscationData> mapper = new HashMap<>();
+
+ /**
+ * The public constructor.
+ * Should be passed a {@link Stream} of Strings, one representing each line.
+ * Whether they contain line endings or not is irrelevant.
+ * @param str a {@link Stream} of strings
+ */
+ public ObfuscationMapper(Stream<String> str) {
+ AtomicReference<String> currentClass = new AtomicReference<>("");
+ str.forEach(l -> {
+ if(l.startsWith("\t"))
+ mapper.get(currentClass.get()).addMember(l);
+ else {
+ String[] sp = l.split(" ");
+ ObfuscationData s = new ObfuscationData(sp[0], sp[1]);
+ currentClass.set(s.unobf);
+ mapper.put(s.unobf, s);
+ }
+ });
+ }
+
+ /**
+ * Gets the obfuscated name of the class.
+ * @param name the unobfuscated internal name of the desired class
+ * @return the obfuscated name of the class
+ * @throws MappingNotFoundException if no mapping is found
+ */
+ public String obfuscateClass(String name) {
+ ObfuscationData data = mapper.get(name);
+ if(data == null)
+ throw new MappingNotFoundException(name);
+ else return data.obf;
+ }
+
+ /**
+ * Gets the unobfuscated name of the class.
+ * Due to how it's implemented, it's considerably less efficient than its
+ * opposite operation.
+ * @param obfName the obfuscated internal name of the desired class
+ * @return the deobfuscated name of the class
+ */
+ public String deobfuscateClass(String obfName) {
+ ObfuscationData data = getObfuscationData(obfName);
+ return data.unobf;
+ }
+
+ /**
+ * Gets the obfuscated name of a class member (field or method).
+ * The method signature must be in this format: "methodName methodDescriptor",
+ * with a space, because that's how it is in .tsrg files.
+ * @param parentName the unobfuscated internal name of the parent class
+ * @param memberName the field name or method signature
+ * @return the obfuscated name of the given member
+ * @throws MappingNotFoundException if no mapping is found
+ */
+ public String obfuscateMember(String parentName, String memberName) {
+ ObfuscationData data = mapper.get(parentName);
+ if(data == null)
+ throw new MappingNotFoundException(parentName + "::" + memberName);
+ return data.members.get(memberName);
+ }
+
+ /**
+ * Gets the unobfuscated name of the given member.
+ * Due to how it's implemented, it's considerably less efficient than its
+ * opposite operation.
+ * @param parentObf the obfuscated internal name of the container class
+ * @param memberObf the field name or method signature
+ * @return the deobfuscated name of the given member
+ */
+ public String deobfuscateMember(String parentObf, String memberObf) {
+ ObfuscationData data = getObfuscationData(parentObf);
+ for(String unobf : data.members.keySet())
+ if(data.members.get(unobf).equals(memberObf))
+ return unobf;
+ return null;
+ }
+
+ /**
+ * Used internally. Gets the obfuscation data corresponding to the given obfuscated class name.
+ * @param classObfuscatedName the internal name of the obfuscated class
+ * @return the desired {@link ObfuscationData} object
+ * @throws MappingNotFoundException if no {@link ObfuscationData} object is found
+ */
+ private ObfuscationData getObfuscationData(String classObfuscatedName) {
+ for(ObfuscationData s : mapper.values())
+ if(s.obf.equals(classObfuscatedName))
+ return s;
+ throw new MappingNotFoundException(classObfuscatedName);
+ }
+
+ /**
+ * Private class used internally for storing information about each
+ * class. It's private because there is no good reason anyone would
+ * want to access this outside of this class.
+ */
+ private static class ObfuscationData {
+ /**
+ * The unobfuscated name (FQN with '/' instad of '.') of the class.
+ */
+ private final String unobf;
+
+ /**
+ * The obfuscated internal name (FQN with '/' instad of '.') of the class.
+ */
+ private final String obf;
+
+ /**
+ * A {@link Map} tying each member's name or signature to its
+ * obfuscated counterpart.
+ */
+ private final Map<String, String> members;
+
+
+ /**
+ * The constructor. It takes in the names (obfuscated and non-obfuscated)
+ * of a class.
+ * @param unobf the unobfuscated name
+ * @param obf the obfuscated name
+ */
+ private ObfuscationData(String unobf, String obf) {
+ this.unobf = unobf;
+ this.obf = obf;
+ this.members = new HashMap<>();
+ }
+
+ /**
+ * Adds a member to the target class.
+ * For fields only the names are required; for methods,
+ * this takes in the full signature ({@code name + " " + space}).
+ * @param s the String representing the declaration line
+ */
+ public void addMember(String s) {
+ String[] split = s.trim().split(" ");
+ if(split.length == 2) //field
+ members.put(split[0], split[1]);
+ else if (split.length == 3) //method
+ members.put(split[0] + " " + split[1], split[2]);
+ }
+ }
+}