package org.mutabilitydetector.checkers.settermethod;
/*
* #%L
* MutabilityDetector
* %%
* Copyright (C) 2008 - 2014 Graham Allan
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.mutabilitydetector.checkers.AccessModifierQuery.field;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.annotation.concurrent.Immutable;
import javax.annotation.concurrent.NotThreadSafe;
import org.mutabilitydetector.checkers.MethodIs;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.MethodNode;
/**
* Collection to hold relations of candidates and initialising methods
* for those.
*
* @author Juergen Fickel (jufickel@htwg-konstanz.de)
* @version 06.02.2013
*/
@NotThreadSafe
final class CandidatesInitialisersMapping implements Iterable<CandidatesInitialisersMapping.Entry> {
public interface Initialisers {
boolean add(MethodNode initialiser);
List<MethodNode> getConstructors();
List<MethodNode> getMethods();
} // interface Setters
@Immutable
private static final class NullInitialisers implements Initialisers {
private static final NullInitialisers INSTANCE = new NullInitialisers();
private NullInitialisers() {
super();
}
public static Initialisers getInstance() {
return INSTANCE;
}
@Override
public boolean add(final MethodNode initialiser) {
return false;
}
@Override
public List<MethodNode> getConstructors() {
return Collections.emptyList();
}
@Override
public List<MethodNode> getMethods() {
return Collections.emptyList();
}
@Override
public String toString() {
return getClass().getSimpleName() + " []";
}
} // class NullSetters
@NotThreadSafe
private static final class DefaultInitialisers implements Initialisers {
private final List<MethodNode> constructors;
private final List<MethodNode> methods;
private DefaultInitialisers() {
final byte initialSize = 2;
constructors = new ArrayList<MethodNode>(initialSize);
methods = new ArrayList<MethodNode>(initialSize);
}
public static Initialisers getInstance() {
return new DefaultInitialisers();
}
@Override
public boolean add(final MethodNode setter) {
final boolean result;
if (isConstructor(setter)) {
result = constructors.add(setter);
} else {
result = methods.add(setter);
}
return result;
}
private static boolean isConstructor(final MethodNode setter) {
return MethodIs.aConstructor(setter.name);
}
@Override
public List<MethodNode> getConstructors() {
return Collections.unmodifiableList(constructors);
}
@Override
public List<MethodNode> getMethods() {
return Collections.unmodifiableList(methods);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + constructors.hashCode();
result = prime * result + methods.hashCode();
return result;
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof DefaultInitialisers)) {
return false;
}
final DefaultInitialisers other = (DefaultInitialisers) obj;
if (!constructors.equals(other.constructors)) {
return false;
}
if (!methods.equals(other.methods)) {
return false;
}
return true;
}
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
builder.append(getClass().getSimpleName()).append(" [");
builder.append("constructors=").append(concatenateMethodNames(constructors));
builder.append(", methods=").append(concatenateMethodNames(methods)).append(']');
return builder.toString();
}
private static String concatenateMethodNames(final List<MethodNode> methods) {
final StringBuilder result = new StringBuilder();
result.append('[');
final String separator = ", ";
String sep = "";
for (final MethodNode method : methods) {
result.append(sep).append(method.name);
sep = separator;
}
result.append(']');
return result.toString();
}
} // class DefaultInitialisers
public interface Entry {
FieldNode getCandidate();
Initialisers getInitialisers();
} // interface Entry
private static final class DefaultEntry implements Entry {
private final FieldNode candidate;
private final Initialisers initialisers;
public DefaultEntry(final FieldNode theCandidate, final Initialisers theInitialisers) {
final String msgTemplate = "Argument '%s' must not be null!";
candidate = checkNotNull(theCandidate, msgTemplate, "theCandidate");
initialisers = checkNotNull(theInitialisers, msgTemplate, "theInitialisers");
}
@Override
public FieldNode getCandidate() {
return candidate;
}
@Override
public Initialisers getInitialisers() {
return initialisers;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + candidate.name.hashCode();
result = prime * result + candidate.desc.hashCode();
result = prime * result + initialisers.hashCode();
return result;
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof DefaultEntry)) {
return false;
}
final DefaultEntry other = (DefaultEntry) obj;
if (!candidate.name.equals(other.candidate.name)) {
return false;
}
if (!candidate.desc.equals(other.candidate.desc)) {
return false;
}
if (!initialisers.equals(other.initialisers)) {
return false;
}
return true;
}
@Override
public String toString() {
final StringBuilder b = new StringBuilder();
b.append(getClass().getSimpleName()).append(" [");
b.append("candidate=").append(candidate.name);
b.append(", initialisers=").append(initialisers);
return b.toString();
}
} // class DefaultEntry
private final ConcurrentMap<FieldNode, Initialisers> candidatesAndInitialisers;
private final Map<String, Set<MethodNode>> visibleSetterMethods;
private CandidatesInitialisersMapping() {
this(new ConcurrentHashMap<FieldNode, Initialisers>());
}
private CandidatesInitialisersMapping(final Map<FieldNode, Initialisers> otherVariableSetters) {
candidatesAndInitialisers = new ConcurrentHashMap<FieldNode, Initialisers>(otherVariableSetters);
visibleSetterMethods = new HashMap<String, Set<MethodNode>>();
}
/**
* @return a new instance of this class.
*/
public static CandidatesInitialisersMapping newInstance() {
return new CandidatesInitialisersMapping();
}
/**
* @param candidate
* candidate to add to this collection. Must not be
* {@code null}.
* @return {@code true} if the node was added, {@code false} if
* the node was already contained in this collection.
*/
public boolean addCandidate(final FieldNode candidate) {
checkNotNull(candidate);
final boolean result = !candidatesAndInitialisers.containsKey(candidate);
candidatesAndInitialisers.put(candidate, DefaultInitialisers.getInstance());
return result;
}
/**
* @param candidateName
* name of the candidate to associate the setter method
* with. Must neither be {@code null} nor {@code ""}.
* @param initialiser
* initialiser to be associated with
* {@code candidateName} Must not be {@code null}.
* @return {@code true} if {@code candidateName} was already part
* of this collection, {@code false} else.
*/
public boolean addInitialiserForCandidate(final String candidateName, final MethodNode initialiser) {
checkArgument(!candidateName.isEmpty());
checkNotNull(initialiser);
boolean result = false;
final FieldNode candidate = getCandidateForName(candidateName);
if (null != candidate) {
result = addInitialiserForCandidate(candidate, initialiser);
} else {
if (isNotAConstructor(initialiser) && isNotPrivate(initialiser)) {
addToVisibleSetterMethods(candidateName, initialiser);
}
}
return result;
}
private FieldNode getCandidateForName(final String candidateName) {
for (final Map.Entry<FieldNode, Initialisers> entry : candidatesAndInitialisers.entrySet()) {
final FieldNode candidate = entry.getKey();
if (candidate.name.equals(candidateName)) {
return candidate;
}
}
return null;
}
private boolean addInitialiserForCandidate(final FieldNode candidate, final MethodNode initialiser) {
final Initialisers initialisersForCandidate = candidatesAndInitialisers.get(candidate);
return initialisersForCandidate.add(initialiser);
}
private static boolean isNotAConstructor(final MethodNode methodOrConstructor) {
final String methodOrConstructorName = methodOrConstructor.name;
return !MethodIs.aConstructor(methodOrConstructorName) && !"<clinit>".equals(methodOrConstructorName);
}
private static boolean isNotPrivate(final MethodNode method) {
return field(method.access).isNotPrivate();
}
private void addToVisibleSetterMethods(final String candidateName, final MethodNode method) {
final Set<MethodNode> setterMethods;
if (visibleSetterMethods.containsKey(candidateName)) {
setterMethods = visibleSetterMethods.get(candidateName);
setterMethods.add(method);
} else {
setterMethods = new HashSet<MethodNode>();
setterMethods.add(method);
visibleSetterMethods.put(candidateName, setterMethods);
}
}
Initialisers getInitialisersFor(final FieldNode candidate) {
checkNotNull(candidate);
Initialisers result = candidatesAndInitialisers.get(candidate);
if (null == result) {
result = NullInitialisers.getInstance();
}
return result;
}
/**
* @param candidateName
* name of the candidate to get all initialising
* methods for. Must neither be {@code null} nor
* {@code ""}.
* @return all initialising methods (not constructors) for the
* candidate with name {@code candidateName}. If none are
* found an empty {@code List} is returned.
*/
Collection<MethodNode> getInitialisingMethodsFor(final String candidateName) {
checkArgument(!candidateName.isEmpty());
Collection<MethodNode> result = Collections.emptyList();
final FieldNode candidate = getCandidateForName(candidateName);
if (null != candidate) {
final Initialisers settersForVariable = candidatesAndInitialisers.get(candidate);
result = settersForVariable.getMethods();
}
return result;
}
public Map<String, Set<MethodNode>> getAllVisibleSetterMethods() {
return Collections.unmodifiableMap(visibleSetterMethods);
}
public FieldNode removeAndGetCandidateForInitialisingMethod(final MethodNode initialisingMethod) {
FieldNode result = null;
final Set<Map.Entry<FieldNode, Initialisers>> entrySet = candidatesAndInitialisers.entrySet();
final Iterator<Map.Entry<FieldNode, Initialisers>> iterator = entrySet.iterator();
while (null == result && iterator.hasNext()) {
final Map.Entry<FieldNode, Initialisers> entry = iterator.next();
final Initialisers initialisers = entry.getValue();
final List<MethodNode> initialisingMethods = initialisers.getMethods();
if (initialisingMethods.contains(initialisingMethod)) {
result = entry.getKey();
iterator.remove();
}
}
return result;
}
/**
* Removes all candidates from this collection which are not
* associated with an initialising method.
*
* @return a {@code Collection} containing the removed
* unassociated candidates. This list is empty if none
* were removed, i. e. the result is never {@code null}.
*/
public Collection<FieldNode> removeAndGetCandidatesWithoutInitialisingMethod() {
final List<FieldNode> result = new ArrayList<FieldNode>();
for (final Map.Entry<FieldNode, Initialisers> entry : candidatesAndInitialisers.entrySet()) {
final Initialisers setters = entry.getValue();
final List<MethodNode> initialisingMethods = setters.getMethods();
if (initialisingMethods.isEmpty()) {
result.add(entry.getKey());
}
}
for (final FieldNode unassociatedVariable : result) {
candidatesAndInitialisers.remove(unassociatedVariable);
}
return result;
}
@Override
public Iterator<Entry> iterator() {
final Set<Entry> entrySet = new HashSet<Entry>(candidatesAndInitialisers.size());
for (final Map.Entry<FieldNode, Initialisers> entry : candidatesAndInitialisers.entrySet()) {
entrySet.add(new DefaultEntry(entry.getKey(), entry.getValue()));
}
return entrySet.iterator();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + candidatesAndInitialisers.hashCode();
return result;
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null) {
return false;
}
if (!(o instanceof CandidatesInitialisersMapping)) {
return false;
}
final CandidatesInitialisersMapping other = (CandidatesInitialisersMapping) o;
if (!candidatesAndInitialisers.equals(other.candidatesAndInitialisers)) {
return false;
}
return true;
}
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
builder.append(getClass().getSimpleName()).append(" [");
builder.append("candidatesAndInitialisers=").append(candidatesAndInitialisersToString());
builder.append(", visibleSetterMethods=").append(visibleSetterMethods).append(']');
return builder.toString();
}
private String candidatesAndInitialisersToString() {
final StringBuilder result = new StringBuilder();
result.append('{');
final String separator = ", ";
String sep = "";
for (final Entry entry : this) {
final FieldNode candidate = entry.getCandidate();
result.append(sep).append(candidate.name).append(": ").append(entry.getInitialisers());
sep = separator;
}
result.append('}');
return result.toString();
}
}