package com.dexesttp.hkxpack.descriptor.reader; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import com.dexesttp.hkxpack.descriptor.HKXDescriptor; import com.dexesttp.hkxpack.descriptor.HKXDescriptorFactory; import com.dexesttp.hkxpack.descriptor.HKXEnumResolver; import com.dexesttp.hkxpack.descriptor.enums.HKXType; import com.dexesttp.hkxpack.descriptor.exceptions.ClassFileReadException; import com.dexesttp.hkxpack.descriptor.exceptions.ClassListReadException; import com.dexesttp.hkxpack.descriptor.members.HKXMemberTemplate; import com.dexesttp.hkxpack.hkx.types.MemberSizeResolver; import com.dexesttp.hkxpack.hkx.types.ObjectSizeResolver; import com.dexesttp.hkxpack.resources.DOMUtils; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; /** * Reads ClassXML and produces HKXDescriptorTemplates from it. */ public class ClassXMLReader { private final transient ClassXMLList classList; private final transient HKXDescriptorFactory descriptorFactory; private final transient HKXEnumResolver enumResolver; ClassXMLReader(final HKXDescriptorFactory descriptorFactory, final ClassXMLList classList, final HKXEnumResolver enumResolver) throws ClassListReadException { this.descriptorFactory = descriptorFactory; this.classList = classList; this.enumResolver = enumResolver; } /** * Retrieves a HKXDescriptor from the classXML files. * @param classname * @return * @throws ClassFileReadException */ public HKXDescriptor get(final String classname) throws ClassFileReadException { // Retrieve the document. Document document = openFile(classname); // Read class Node classNode = document.getFirstChild(); List<HKXMemberTemplate> memberList = new ArrayList<>(); // Read signature String signatureString = DOMUtils.getNodeAttr("signature", classNode); long signature = Long.parseLong(signatureString.substring(2), 16); // Read enums NodeList enums = document.getElementsByTagName("enums"); if(enums.getLength() > 0) { retrieveEnums(classname, enums); } // Handle eventual parent String parentName = DOMUtils.getNodeAttr("parent", classNode); if(!parentName.isEmpty()) { HKXDescriptor parent = descriptorFactory.get(parentName); memberList.addAll(parent.getMemberTemplates()); } // Handle direct members. NodeList members = document.getElementsByTagName("member"); for(int i = 0; i < members.getLength(); i++) { Node memberNode = members.item(i); memberList.addAll(resolveMember(memberNode, classname)); } // Return the descriptor return new HKXDescriptor(classname, signature, memberList); } /** * Retrieve all the enumerations described as {@link Node} in the given {@link NodeList}, and store them in this {@link ClassXMLReader}'s {@link HKXEnumResolver}. * @param classname the classname currently read. * @param enums the enumeration node list. */ private void retrieveEnums(final String classname, final NodeList enums) { StringBuffer enumNameBuffer = new StringBuffer(); NodeList enumsObjects = enums.item(0).getChildNodes(); for(int i = 0; i < enumsObjects.getLength(); i++) { Node enumObject = enumsObjects.item(i); if(enumObject.getAttributes() != null) { Map<Integer, String> enumContents = createHashMap(); enumNameBuffer.setLength(0); String enumName = enumNameBuffer .append(classname) .append(".") .append(DOMUtils.getNodeAttr("name", enumObject)) .toString(); for(int j = 0; j < enumObject.getChildNodes().getLength(); j++) { Node enumObjectContent = enumObject.getChildNodes().item(j); if(enumObjectContent.getAttributes() != null) { String enumObjectName = DOMUtils.getNodeAttr("name", enumObjectContent); int enumObjectContents = Integer.parseInt(DOMUtils.getNodeAttr("value", enumObjectContent)); enumContents.put(enumObjectContents, enumObjectName); } } BiMap<Integer, String> test = HashBiMap.create(enumContents); enumResolver.add(enumName, test.inverse()); } } } private Document openFile(final String classname) throws ClassFileReadException { Document document; try { String classUri = classList.getFileName(classname); if(classUri == null) { return null; } URL source = ClassXMLReader.class.getResource(classUri); DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); document = builder.parse(source.openStream()); } catch (SAXException | ParserConfigurationException e) { throw new ClassFileReadException("Couldn't parse file for " + classname + " : invalid DOM.", e); } catch (IOException e) { throw new ClassFileReadException("Error reading file for " + classname + ".", e); } return document; } private List<HKXMemberTemplate> resolveMember(final Node memberNode, final String classname) throws ClassFileReadException { String name = DOMUtils.getNodeAttr("name", memberNode); String offset = DOMUtils.getNodeAttr("offset", memberNode); String vtype = DOMUtils.getNodeAttr("vtype", memberNode); String vsubtype = DOMUtils.getNodeAttr("vsubtype", memberNode); String ctype = DOMUtils.getNodeAttr("ctype", memberNode); StringBuilder etypeBuilder = new StringBuilder(DOMUtils.getNodeAttr("etype", memberNode)); if(etypeBuilder.length() > 0) { etypeBuilder.insert(0, classname + "."); } String etype = etypeBuilder.toString(); String arrsize = DOMUtils.getNodeAttr("arrsize", memberNode); String flags = DOMUtils.getNodeAttr("flags", memberNode); List<HKXMemberTemplate> res = new ArrayList<HKXMemberTemplate>(); if(arrsize.equals("0")) { HKXMemberTemplate template = new HKXMemberTemplate(name, offset, vtype, vsubtype, ctype, etype, arrsize, flags); res.add(template); } else { int size = Integer.parseInt(arrsize); long memberSize = 0; if(vtype.equals("TYPE_STRUCT")) { memberSize = ObjectSizeResolver.getSize(descriptorFactory.get(ctype), descriptorFactory); } else { memberSize = MemberSizeResolver.getSize(HKXType.valueOf(vtype)); } long memberOffset = Integer.parseInt(offset); HMTBuilder builder = new HMTBuilder(name, vtype, vsubtype, ctype, etype, arrsize, flags); for(int i = 0; i < size; i++) { res.add(builder.build(i, memberOffset, memberSize)); } } return res; } /** * Builds a {@link HKXMemberTemplate} at each iteration */ private class HMTBuilder { private final transient String name; private final transient String vtype; private final transient String vsubtype; private final transient String ctype; private final transient String etype; private final transient String arrsize; private final transient String flags; HMTBuilder(final String name, final String vtype, final String vsubtype, final String ctype, final String etype, final String arrsize, final String flags) { this.name = name; this.vtype = vtype; this.vsubtype = vsubtype; this.ctype = ctype; this.etype = etype; this.arrsize = arrsize; this.flags = flags; } HKXMemberTemplate build(final int i, final long memberOffset, final long memberSize) { return new HKXMemberTemplate( name + (i+1), Long.toString(memberOffset + i * memberSize), vtype, vsubtype, ctype, etype, arrsize, flags); } } /** * That's a shame that PMD prefers this. I see the point though, as it allows to easily zero-in on object creation. * @return */ private Map<Integer, String> createHashMap() { return new HashMap<>(); } }