Java tutorial
/* * This file is part of OverMapped. * * OverMapped is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OverMapped is distributed in the hope that it will be * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with OverMapped. If not, see <http://www.gnu.org/licenses/>. */ package com.wolvereness.overmapped; import static com.google.common.collect.Lists.*; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.maven.plugin.MojoFailureException; import org.objectweb.asm.commons.Remapper; import com.google.common.collect.BiMap; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; import com.google.common.collect.Multimap; import com.wolvereness.overmapped.asm.ByteClass; import com.wolvereness.overmapped.asm.Signature; import com.wolvereness.overmapped.asm.Signature.MutableSignature; class MembersSubRoutine extends SubRoutine { static class Store { String newName; String oldName; String description; String originalDescription; final Set<String> searchCache; final Set<String> parents; Map<String, Signature> classFieldsCache; final OverMapped instance; Store(final Set<String> searchCache, final Set<String> parents, final OverMapped instance) { this.searchCache = searchCache; this.parents = parents; this.instance = instance; } } MembersSubRoutine() { super("members"); } @Override public void invoke(final OverMapped instance, final Map<String, ByteClass> classes, final Multimap<String, String> depends, final Multimap<String, String> rdepends, final BiMap<String, String> nameMaps, final BiMap<String, String> inverseNameMaps, final BiMap<Signature, Signature> signatureMaps, final BiMap<Signature, Signature> inverseSignatureMaps, final Remapper inverseMapper, final MutableSignature mutableSignature, final Set<String> searchCache, final Map<Signature, Integer> flags, final Map<?, ?> map) throws ClassCastException, NullPointerException, MojoFailureException { final Object memberMaps = map.get(tag); if (!(memberMaps instanceof Map)) return; final Store store = new Store(searchCache, instance.isFindParents() ? new HashSet<String>() : null, instance); for (final Map.Entry<?, ?> memberMap : ((Map<?, ?>) memberMaps).entrySet()) { final Map<?, ?> maps = asType(memberMap.getValue(), "`%4$s' points to a %2$s `%1$s', expected a %5$s, in `%3$s'", false, memberMaps, memberMap, Map.class); if (memberMap.getKey() instanceof Collection<?> && ((Collection<?>) memberMap.getKey()).size() > 1) { final Iterable<String> classNames; { final ImmutableCollection.Builder<String> containingClassNames = ImmutableList.builder(); for (final Object clazz : (Collection<?>) memberMap.getKey()) { final String unresolvedClassName = asType(clazz, "`%4$s' contains a %2$s `%1$s', expected a %5$s, from `%3$s'", false, memberMaps, memberMap.getKey(), String.class); final String className = inverseNameMaps.get(unresolvedClassName); if (className == null) { instance.missingAction.actMemberClass(instance.getLog(), unresolvedClassName, memberMap.getKey(), inverseNameMaps); continue; } containingClassNames.add(className); } classNames = containingClassNames.build(); } for (final Map.Entry<?, ?> entry : maps.entrySet()) { parseMapping(store, inverseMapper, mutableSignature, maps, entry, false); final String newName = store.newName, oldName = store.oldName, description = store.description, originalDescription = store.originalDescription; if (!mutableSignature.update("", "", description).isMethod()) throw new MojoFailureException(String .format("Malformed mapping %s for %s; can only map methods.", entry, memberMap)); for (final String className : classNames) { updateMember(store, signatureMaps, inverseSignatureMaps, mutableSignature, oldName, newName, description, className, nameMaps, originalDescription, nameMaps.get(className)); if (mutableSignature.isMethod() && !mutableSignature.isConstructor()) { final Set<String> parents = store.parents; if (parents != null) { parents.addAll(depends.get(className)); } for (final String inherited : rdepends.get(className)) { if (!updateMember(store, signatureMaps, inverseSignatureMaps, mutableSignature, oldName, newName, description, inherited, nameMaps, originalDescription, nameMaps.get(inherited))) continue; if (parents != null) { parents.addAll(depends.get(inherited)); } } } } performParentChecks(store, nameMaps, inverseSignatureMaps, mutableSignature, classNames, newName, oldName, description, originalDescription); store.searchCache.clear(); } continue; } if (memberMap.getKey() instanceof Collection<?> && ((Collection<?>) memberMap.getKey()).size() < 1) throw new MojoFailureException( String.format("Malformed mapping %s -> %s", memberMap.getKey(), maps)); final String unresolvedClassName = asType( memberMap.getKey() instanceof Collection<?> ? ((Collection<?>) memberMap.getKey()).iterator().next() : memberMap.getKey(), "`%4$s' points from a %2$s `%1$s', expected a %5$s, in `%3$s'", false, memberMaps, memberMap, String.class); final String className = inverseNameMaps.get(unresolvedClassName); if (className == null) { instance.missingAction.actMemberClass(instance.getLog(), unresolvedClassName, memberMap.getKey(), inverseNameMaps); continue; } for (final Map.Entry<?, ?> entry : maps.entrySet()) { processSingleClassMappings(store, classes, depends, rdepends, nameMaps, signatureMaps, inverseSignatureMaps, inverseMapper, mutableSignature, maps, className, unresolvedClassName, entry); } } } private static void processSingleClassMappings(final Store store, final Map<String, ByteClass> classes, final Multimap<String, String> depends, final Multimap<String, String> rdepends, final BiMap<String, String> nameMaps, final BiMap<Signature, Signature> signatureMaps, final BiMap<Signature, Signature> inverseSignatureMaps, final Remapper inverseMapper, final MutableSignature mutableSignature, final Map<?, ?> maps, final String className, final String originalClassName, final Map.Entry<?, ?> entry) throws MojoFailureException { if (entry.getValue() instanceof String) { parseMapping(store, inverseMapper, mutableSignature, maps, entry, true); final String newName = store.newName, oldName = store.oldName, description = store.description, originalDescription = store.originalDescription; if (description == null) { final Map<String, Signature> classFieldsCache = buildFieldsCache(store, classes.get(className).getLocalSignatures(), signatureMaps); final Signature signature = getClassField(store, classFieldsCache, oldName, originalClassName); if (signature == null) return; attemptFieldMap(signatureMaps, signature, mutableSignature, oldName, newName, className); return; } updateMember(store, signatureMaps, inverseSignatureMaps, mutableSignature, oldName, newName, description, className, nameMaps, originalDescription, originalClassName); if (mutableSignature.isMethod() && !mutableSignature.isConstructor()) { final Set<String> parents = store.parents; if (parents != null) { parents.addAll(depends.get(className)); } for (final String inherited : rdepends.get(className)) { if (!updateMember(store, signatureMaps, inverseSignatureMaps, mutableSignature, oldName, newName, description, inherited, nameMaps, originalDescription, nameMaps.get(inherited))) continue; if (parents != null) { parents.addAll(depends.get(inherited)); } } performParentChecks(store, nameMaps, inverseSignatureMaps, mutableSignature, className, newName, oldName, description, originalDescription); } store.searchCache.clear(); } else if (entry.getValue() instanceof Iterable) { final Map<String, Signature> classFieldsCache = buildFieldsCache(store, classes.get(className).getLocalSignatures(), signatureMaps); final Iterable<?> names = (Iterable<?>) entry.getValue(); final List<?> oldNames; final int start; { final Map<?, ?> entryMap = asType(entry.getKey(), "`%4$s' points from a %2$s `%1$s', expected a %5$s, in `%3$s'", false, maps, entry, Map.class); if (entryMap.size() != 1) throw new MojoFailureException(String.format("Malformed mapping `%s' to `%s' in `%s'", entryMap, entry.getValue(), maps)); final Map.Entry<?, ?> pair = entryMap.entrySet().iterator().next(); oldNames = asType(pair.getKey(), "`%4$s' points from a %2$s `%1$s', expected a %5$s, in `%3$s'", false, entry, pair, List.class); final String startToken = asType(pair.getValue(), "`%4$s' points to a %2$s `%1$s', expected a %5$s, in `%3$s'", false, entry, pair, String.class); start = oldNames.indexOf(startToken); if (start == -1) throw new MojoFailureException( String.format("Cannot find value `%s' in `%s'", startToken, oldNames)); } int i = start; for (final Object name : names) { if (i >= oldNames.size()) throw new MojoFailureException( String.format("Insuffient sequence length %n in `%s' for `%s' at `%s'", i, oldNames, name, names, name)); final String newName = asType(name, "`%4$s' contains a %2$s `%1$s', expected a %5$s, in `%3$s'", false, entry, names, String.class); final String oldName = asType(oldNames.get(i++), "`%4$s' contains a %2$s `%1$s', expected a %5$s, in `%3$s'", false, entry, oldNames, String.class); final Signature signature = getClassField(store, classFieldsCache, oldName, originalClassName); if (signature == null) { continue; } attemptFieldMap(signatureMaps, signature, mutableSignature, oldName, newName, className); updateFieldCacheEntry(classFieldsCache, signature, newName); } } else throw new MojoFailureException(String.format("Malformed mapping `%s' from `%s' in `%s'", entry.getValue(), entry.getKey(), maps)); } private static void updateFieldCacheEntry(final Map<String, Signature> cache, final Signature signature, final String newName) { final int size = cache.size(); if (cache.put(newName, signature) != null || size == cache.size()) { // (put() != null) is redundant to (size==cache.size()), but who cares? cache.put(newName, null); } } private static Signature getClassField(final Store store, final Map<String, Signature> classFieldsCache, final String oldName, final String originalClassName) throws MojoFailureException { final int size = classFieldsCache.size(); final Signature signature = classFieldsCache.remove(oldName); if (signature != null) return signature; if (size != classFieldsCache.size()) throw new MojoFailureException(String.format("Ambiguous field name %s", oldName)); store.instance.missingAction.actField(store.instance.getLog(), classFieldsCache, oldName, originalClassName); return null; } private static void parseMapping(final Store store, final Remapper inverseMapper, final MutableSignature mutableSignature, final Map<?, ?> maps, final Map.Entry<?, ?> entry, boolean nullDescription) throws MojoFailureException { store.newName = asType(entry.getValue(), "`%4$s' points to a %2$s `%1$s', expected a %5$s, in `%3$s'", false, maps, entry, String.class); if (entry.getKey() instanceof Map && ((Map<?, ?>) entry.getKey()).size() == 1) { final Map.Entry<?, ?> mapKey = ((Map<?, ?>) entry.getKey()).entrySet().iterator().next(); store.oldName = asType(mapKey.getKey(), "`%4$s' points from a %2$s `%1$s', expected a %5$s, in `%3$s'", false, entry.getKey(), mapKey, String.class); final String unresolvedDescription = store.originalDescription = asType(mapKey.getValue(), "`%4$s' points to a %2$s `%1$s', expected a %5$s, in `%3$s'", nullDescription, entry.getKey(), mapKey, String.class); store.description = parseDescription(inverseMapper, mutableSignature, unresolvedDescription); } else if (entry.getKey() instanceof String) { final String fullToken = (String) entry.getKey(); final int split = fullToken.indexOf(' '); final String unresolvedDescription; if (nullDescription & (nullDescription = (split == -1))) { unresolvedDescription = null; store.oldName = fullToken; } else if (nullDescription || split != fullToken.lastIndexOf(' ')) throw new MojoFailureException(String.format("Malformed mapping %s", fullToken)); else { unresolvedDescription = store.originalDescription = fullToken.substring(split + 1, fullToken.length()); store.oldName = fullToken.substring(0, split); } store.description = parseDescription(inverseMapper, mutableSignature, unresolvedDescription); } else throw new MojoFailureException( String.format("Malformed mapping `%s' to `%s' in `%s'", entry.getKey(), store.newName, maps)); } private static void performParentChecks(final Store store, final BiMap<String, String> nameMaps, final BiMap<Signature, Signature> inverseSignatureMaps, final MutableSignature mutableSignature, Object className_s, final String newName, final String oldName, final String description, final String originalDescription) { final Set<String> parents = store.parents; if (parents != null) { parents.removeAll(store.searchCache); if (parents.isEmpty()) return; if (className_s instanceof String) { className_s = nameMaps.get(className_s); } else { final Collection<String> originalClassNames = newArrayList(); for (final Object className : (Iterable<?>) className_s) { originalClassNames.add(nameMaps.get(className)); } className_s = originalClassNames; } for (final String parent : parents) { if (inverseSignatureMaps.containsKey(mutableSignature.update(parent, oldName, description))) { store.instance.getLog() .info(String.format("Expected parent method mapping for `%s'->`%s' from mappings in %s", mutableSignature.update(nameMaps.get(parent), oldName, originalDescription), mutableSignature.forElementName(newName), className_s)); } } parents.clear(); } } private static void attemptFieldMap(final BiMap<Signature, Signature> signatureMaps, final Signature signature, final MutableSignature mutableSignature, final String oldName, final String newName, final String className) throws MojoFailureException { try { signatureMaps.put(signature, signature.forElementName(newName)); } catch (final IllegalArgumentException ex) { final MojoFailureException mojoEx = new MojoFailureException( String.format("Cannot map %s (currently %s) to pre-existing member %s (in class %s)", signature, mutableSignature.update(className, oldName, signature.getDescriptor()), signature.forElementName(newName), className)); mojoEx.initCause(ex); throw mojoEx; } } private static String parseDescription(final Remapper inverseMapper, final MutableSignature mutableSignature, final String unresolvedDescription) { return unresolvedDescription != null ? mutableSignature.update("", "", unresolvedDescription).isMethod() ? inverseMapper.mapMethodDesc(unresolvedDescription) : inverseMapper.mapDesc(unresolvedDescription) : null; } private static Map<String, Signature> buildFieldsCache(final Store store, final List<Signature> localSignatures, final Map<Signature, Signature> signatures) { Map<String, Signature> classFieldsCache = store.classFieldsCache; if (classFieldsCache == null) { classFieldsCache = store.classFieldsCache = new HashMap<String, Signature>(localSignatures.size()); } else { classFieldsCache.clear(); } int size = 0; for (final Signature signature : localSignatures) { if (signature.isMethod()) { continue; } final String mappedName = signatures.get(signature).getElementName(); if (classFieldsCache.put(mappedName, signature) != null || size == (size = classFieldsCache.size())) { // Remove the mapping we accidentally put in... classFieldsCache.put(mappedName, null); } } return classFieldsCache; } /** * * @return true if cache does not contain original (or similarly interpreted as attempted to update as it has not attempted to do so thus far) */ private static boolean updateMember(final Store store, final BiMap<Signature, Signature> signatureMaps, final BiMap<Signature, Signature> inverseSignatureMaps, final Signature.MutableSignature signature, final String oldName, final String newName, final String description, final String clazz, final Map<String, String> classes, final String originalDescription, final String originalClass) throws MojoFailureException { if (!store.searchCache.add(clazz)) return false; signature.update(clazz, oldName, description); final Signature originalSignature = inverseSignatureMaps.get(signature); if (originalSignature != null) { try { signatureMaps.put(originalSignature, signature.forElementName(newName)); } catch (final IllegalArgumentException ex) { final MojoFailureException mojoEx = new MojoFailureException(String.format( "Cannot map %s (currently %s) to pre-existing member %s (in class %s)", originalSignature, signature, signature.forElementName(newName), classes.get(clazz))); mojoEx.initCause(ex); throw mojoEx; } } else { store.instance.missingAction.actMember(store.instance.getLog(), originalClass, oldName, newName, originalDescription, inverseSignatureMaps); } return true; } }