mirror of
https://github.com/dmulloy2/ProtocolLib.git
synced 2024-09-27 14:52:52 +02:00
258 lines
7.3 KiB
Java
258 lines
7.3 KiB
Java
/*
|
|
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
|
* Copyright (C) 2012 Kristian S. Stangeland
|
|
*
|
|
* This program 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 2 of
|
|
* the License, or (at your option) any later version.
|
|
*
|
|
* This program 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 this program;
|
|
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
|
* 02111-1307 USA
|
|
*/
|
|
|
|
package com.comphenix.protocol.reflect.cloning;
|
|
|
|
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
|
import com.comphenix.protocol.reflect.instances.ExistingGenerator;
|
|
import com.comphenix.protocol.reflect.instances.InstanceProvider;
|
|
import com.google.common.base.Function;
|
|
|
|
import java.lang.ref.WeakReference;
|
|
import java.util.ArrayList;
|
|
import java.util.Collection;
|
|
import java.util.Collections;
|
|
import java.util.List;
|
|
|
|
/**
|
|
* Implements a cloning procedure by trying multiple methods in turn until one is successful.
|
|
*
|
|
* @author Kristian
|
|
*/
|
|
public class AggregateCloner implements Cloner {
|
|
/**
|
|
* Supplies the cloner factories with necessary parameters.
|
|
*
|
|
* @author Kristian
|
|
*/
|
|
public static class BuilderParameters {
|
|
// Can only be modified by the builder
|
|
private InstanceProvider instanceProvider;
|
|
private Cloner aggregateCloner;
|
|
|
|
// Used to construct the different types
|
|
private InstanceProvider typeConstructor;
|
|
|
|
private BuilderParameters() {
|
|
// Only allow inner classes to construct it.
|
|
}
|
|
|
|
/**
|
|
* Retrieve the instance provider last set in the builder.
|
|
* @return Current instance provider.
|
|
*/
|
|
public InstanceProvider getInstanceProvider() {
|
|
return instanceProvider;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the aggregate cloner that is being built.
|
|
* @return The parent cloner.
|
|
*/
|
|
public Cloner getAggregateCloner() {
|
|
return aggregateCloner;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Represents a builder for aggregate (combined) cloners.
|
|
*
|
|
* @author Kristian
|
|
*/
|
|
public static class Builder {
|
|
private final List<Function<BuilderParameters, Cloner>> factories = new ArrayList<>();
|
|
private final BuilderParameters parameters;
|
|
|
|
/**
|
|
* Create a new aggregate builder.
|
|
*/
|
|
public Builder() {
|
|
this.parameters = new BuilderParameters();
|
|
}
|
|
|
|
/**
|
|
* Set the instance provider supplied to all cloners in this builder.
|
|
* @param provider - new instance provider.
|
|
* @return The current builder.
|
|
*/
|
|
public Builder instanceProvider(InstanceProvider provider) {
|
|
this.parameters.instanceProvider = provider;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Add the next cloner that will be considered in turn.
|
|
* @param type - the type of the next cloner.
|
|
* @return This builder.
|
|
*/
|
|
public Builder andThen(final Class<? extends Cloner> type) {
|
|
// Use reflection to generate a factory on the fly
|
|
return andThen(param -> {
|
|
final Object result = param.typeConstructor.create(type);
|
|
|
|
if (result == null) throw new IllegalStateException("Constructed NULL instead of " + type);
|
|
|
|
if (type.isAssignableFrom(result.getClass())) return (Cloner) result;
|
|
else throw new IllegalStateException("Constructed " + result.getClass() + " instead of " + type);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Add the next cloner that will be considered in turn.
|
|
* @param factory - factory constructing the next cloner.
|
|
* @return This builder.
|
|
*/
|
|
public Builder andThen(Function<BuilderParameters, Cloner> factory) {
|
|
factories.add(factory);
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Build a new aggregate cloner using the supplied values.
|
|
* @return A new aggregate cloner.
|
|
*/
|
|
public AggregateCloner build() {
|
|
AggregateCloner newCloner = new AggregateCloner();
|
|
|
|
// The parameters we will pass to our cloners
|
|
Cloner paramCloner = new NullableCloner(newCloner);
|
|
InstanceProvider paramProvider = parameters.instanceProvider;
|
|
|
|
// Initialize parameters
|
|
parameters.aggregateCloner = paramCloner;
|
|
parameters.typeConstructor = DefaultInstances.fromArray(
|
|
ExistingGenerator.fromObjectArray(new Object[] { paramCloner, paramProvider })
|
|
);
|
|
|
|
// Build every cloner in the correct order
|
|
final List<Cloner> cloners = new ArrayList<>();
|
|
|
|
for (int i = 0; i < factories.size(); i++) {
|
|
Cloner cloner = factories.get(i).apply(parameters);
|
|
|
|
// See if we were successful
|
|
if (cloner != null) cloners.add(cloner);
|
|
else throw new IllegalArgumentException(String.format("Cannot create cloner from %s (%s)", factories.get(i), i));
|
|
}
|
|
|
|
// We're done
|
|
newCloner.setCloners(cloners);
|
|
return newCloner;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Represents a default aggregate cloner.
|
|
*/
|
|
public static final AggregateCloner DEFAULT = newBuilder().
|
|
instanceProvider(DefaultInstances.DEFAULT).
|
|
andThen(BukkitCloner.class).
|
|
andThen(ImmutableDetector.class).
|
|
andThen(CollectionCloner.class).
|
|
andThen(FieldCloner.class).
|
|
build();
|
|
|
|
// List of clone methods
|
|
private List<Cloner> cloners;
|
|
|
|
private WeakReference<Object> lastObject;
|
|
private int lastResult;
|
|
|
|
/**
|
|
* Begins constructing a new aggregate cloner.
|
|
* @return A builder for a new aggregate cloner.
|
|
*/
|
|
public static Builder newBuilder() {
|
|
return new Builder();
|
|
}
|
|
|
|
/**
|
|
* Construct a new, empty aggregate cloner.
|
|
*/
|
|
private AggregateCloner() {
|
|
// Only used by our builder above.
|
|
}
|
|
|
|
/**
|
|
* Retrieves a view of the current list of cloners.
|
|
* @return Current cloners.
|
|
*/
|
|
public List<Cloner> getCloners() {
|
|
return Collections.unmodifiableList(cloners);
|
|
}
|
|
|
|
/**
|
|
* Set the cloners that will be used.
|
|
* @param cloners - the cloners that will be used.
|
|
*/
|
|
private void setCloners(Collection<? extends Cloner> cloners) {
|
|
this.cloners = new ArrayList<>(cloners);
|
|
}
|
|
|
|
@Override
|
|
public boolean canClone(Object source) {
|
|
// Optimize a bit
|
|
lastResult = getFirstCloner(source);
|
|
lastObject = new WeakReference<>(source);
|
|
return lastResult >= 0 && lastResult < cloners.size();
|
|
}
|
|
|
|
/**
|
|
* Retrieve the index of the first cloner capable of cloning the given object.
|
|
* <p>
|
|
* Returns an invalid index if no cloner is able to clone the object.
|
|
* @param source - the object to clone.
|
|
* @return The index of the cloner object.
|
|
*/
|
|
private int getFirstCloner(Object source) {
|
|
for (int i = 0; i < cloners.size(); i++) {
|
|
if (cloners.get(i).canClone(source))
|
|
return i;
|
|
}
|
|
|
|
return cloners.size();
|
|
}
|
|
|
|
@Override
|
|
public Object clone(Object source) {
|
|
if (source == null)
|
|
throw new IllegalAccessError("source cannot be NULL.");
|
|
int index = 0;
|
|
|
|
// Are we dealing with the same object?
|
|
if (lastObject != null && lastObject.get() == source) {
|
|
index = lastResult;
|
|
} else {
|
|
index = getFirstCloner(source);
|
|
}
|
|
|
|
// Make sure the object is valid
|
|
if (index < cloners.size()) {
|
|
Cloner cloner = cloners.get(index);
|
|
|
|
// try {
|
|
return cloner.clone(source);
|
|
// } catch (Exception ex) {
|
|
// throw new RuntimeException("Failed to clone " + source + " (" + source.getClass() + ") with " + cloner, ex);
|
|
// }
|
|
}
|
|
|
|
// Damn - failure
|
|
throw new IllegalArgumentException("Cannot clone " + source + " ( " + source.getClass() + "): No cloner is suitable.");
|
|
}
|
|
}
|