/******************************************************************************* * * Copyright (c) 2004-2009 Oracle Corporation. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * * Kohsuke Kawaguchi * * *******************************************************************************/ package hudson.util; import com.thoughtworks.xstream.converters.Converter; import com.thoughtworks.xstream.converters.MarshallingContext; import com.thoughtworks.xstream.converters.UnmarshallingContext; import com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter; import com.thoughtworks.xstream.io.HierarchicalStreamReader; import com.thoughtworks.xstream.io.HierarchicalStreamWriter; import com.thoughtworks.xstream.mapper.Mapper; import hudson.model.AbstractProject; import hudson.model.DependecyDeclarer; import hudson.model.DependencyGraph; import hudson.model.Describable; import hudson.model.Descriptor; import hudson.model.Descriptor.FormException; import hudson.model.Saveable; import net.sf.json.JSONObject; import org.kohsuke.stapler.StaplerRequest; import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; /** * Persisted list of {@link Describable}s with some operations specific to * {@link Descriptor}s. * * <p> This class allows multiple instances of the same descriptor. Some clients * use this semantics, while other clients use it as "up to one instance per one * descriptor" model. * * Some of the methods defined in this class only makes sense in the latter * model, such as {@link #remove(Descriptor)}. * * @author Kohsuke Kawaguchi */ public class DescribableList<T extends Describable<T>, D extends Descriptor<T>> extends PersistedList<T> implements Serializable{ protected DescribableList() { } /** * @deprecated since 2008-08-15. Use {@link #DescribableList(Saveable)} */ public DescribableList(Owner owner) { setOwner(owner); } public DescribableList(Saveable owner) { setOwner(owner); } public DescribableList(Saveable owner, Collection<? extends T> initialList) { super(initialList); setOwner(owner); } /** * @deprecated since 2008-08-15. Use {@link #setOwner(Saveable)} */ public void setOwner(Owner owner) { this.owner = owner; } /** * Removes all instances of the same type, then add the new one. */ public void replace(T item) throws IOException { removeAll((Class) item.getClass()); data.add(item); onModified(); } public T get(D descriptor) { for (T t : data) { if (t.getDescriptor() == descriptor) { return t; } } return null; } public boolean contains(D d) { return get(d) != null; } public void remove(D descriptor) throws IOException { for (T t : data) { if (t.getDescriptor() == descriptor) { data.remove(t); onModified(); return; } } } /** * Creates a detached map from the current snapshot of the data, keyed from * a descriptor to an instance. */ @SuppressWarnings("unchecked") public Map<D, T> toMap() { return (Map) Descriptor.toMap(data); } /** * Rebuilds the list by creating a fresh instances from the submitted form. * <p/> * < * p/> * This method is almost always used by the owner. This method does not * invoke the save method. * * @param json Structured form data that includes the data for nested * descriptor list. * @deprecated as of 2.2.0, use * {@link DescribableListUtil#buildFromJson(hudson.model.Saveable, org.kohsuke.stapler.StaplerRequest, net.sf.json.JSONObject, java.util.List)} */ public void rebuild(StaplerRequest req, JSONObject json, List<? extends Descriptor<T>> descriptors) throws FormException, IOException { List<T> newList = new ArrayList<T>(); for (Descriptor<T> d : descriptors) { String name = d.getJsonSafeClassName(); if (json.has(name)) { T instance = d.newInstance(req, json.getJSONObject(name)); newList.add(instance); } } replaceBy(newList); } /** * @deprecated as of 1.271 Use * {@link #rebuild(StaplerRequest, JSONObject, List)} instead. */ public void rebuild(StaplerRequest req, JSONObject json, List<? extends Descriptor<T>> descriptors, String prefix) throws FormException, IOException { rebuild(req, json, descriptors); } /** * Rebuilds the list by creating a fresh instances from the submitted form. * <p/> * This version works with the the <f:hetero-list> UI tag, where the user * is allowed to create multiple instances of the same descriptor. Order is * also significant. * * @deprecated as of 2.2.0, use * {@link DescribableListUtil#buildFromHetero(hudson.model.Saveable, org.kohsuke.stapler.StaplerRequest, net.sf.json.JSONObject, String, java.util.Collection)} * or * {@link Descriptor#newInstancesFromHeteroList(org.kohsuke.stapler.StaplerRequest, net.sf.json.JSONObject, String, java.util.Collection)} */ public void rebuildHetero(StaplerRequest req, JSONObject formData, Collection<? extends Descriptor<T>> descriptors, String key) throws FormException, IOException { replaceBy(Descriptor.newInstancesFromHeteroList(req, formData, key, descriptors)); } /** * Picks up {@link DependecyDeclarer}s and allow it to build dependencies. */ public void buildDependencyGraph(AbstractProject owner, DependencyGraph graph) { for (Object o : this) { if (o instanceof DependecyDeclarer) { DependecyDeclarer dd = (DependecyDeclarer) o; dd.buildDependencyGraph(owner, graph); } } } /* The following two seemingly pointless method definitions are necessary to produce backward compatible binary signatures. Without this we only get get(Ljava/lang/Class;)Ljava/lang/Object; from PersistedList where we need get(Ljava/lang/Class;)Lhudson/model/Describable; */ public <U extends T> U get(Class<U> type) { return super.get(type); } public T[] toArray(T[] array) { return super.toArray(array); } /** * @deprecated since 2008-08-15. Just implement {@link Saveable}. */ public interface Owner extends Saveable { } /** * {@link Converter} implementation for XStream. * * Serializaion form is compatible with plain {@link List}. */ public static class ConverterImpl extends AbstractCollectionConverter { CopyOnWriteList.ConverterImpl copyOnWriteListConverter; public ConverterImpl(Mapper mapper) { super(mapper); copyOnWriteListConverter = new CopyOnWriteList.ConverterImpl(mapper()); } public boolean canConvert(Class type) { // handle subtypes in case the onModified method is overridden. return DescribableList.class.isAssignableFrom(type); } public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) { for (Object o : (DescribableList) source) { writeItem(o, context, writer); } } public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { CopyOnWriteList core = copyOnWriteListConverter.unmarshal(reader, context); try { DescribableList r = (DescribableList) context.getRequiredType().newInstance(); r.data.replaceBy(core); return r; } catch (InstantiationException e) { InstantiationError x = new InstantiationError(); x.initCause(e); throw x; } catch (IllegalAccessException e) { IllegalAccessError x = new IllegalAccessError(); x.initCause(e); throw x; } } } }