001    /*
002     * Jpkg - Java library and tools for operating system package creation.
003     *
004     * Copyright (c) 2007 Three Rings Design, Inc.
005     * All rights reserved.
006     *
007     * Redistribution and use in source and binary forms, with or without
008     * modification, are permitted provided that the following conditions
009     * are met:
010     * 1. Redistributions of source code must retain the above copyright
011     *    notice, this list of conditions and the following disclaimer.
012     * 2. Redistributions in binary form must reproduce the above copyright
013     *    notice, this list of conditions and the following disclaimer in the
014     *    documentation and/or other materials provided with the distribution.
015     * 3. Neither the name of the copyright owner nor the names of contributors
016     *    may be used to endorse or promote products derived from this software
017     *    without specific prior written permission.
018     *
019     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
020     * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
021     * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
022     * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
023     * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
024     * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
025     * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
026     * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
027     * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
028     * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
029     * POSSIBILITY OF SUCH DAMAGE.
030     */
031    package com.threerings.antidote.field;
032    
033    import java.util.ArrayList;
034    import java.util.Arrays;
035    import java.util.List;
036    
037    import org.apache.tools.ant.Project;
038    import org.apache.tools.ant.ProjectComponent;
039    import org.apache.tools.ant.Task;
040    import org.apache.tools.ant.types.Reference;
041    
042    import com.threerings.antidote.ValidStatus;
043    import com.threerings.antidote.Violation;
044    import com.threerings.antidote.property.Property;
045    
046    import static com.threerings.antidote.MutabilityHelper.objectIsNotSet;
047    import static com.threerings.antidote.MutabilityHelper.objectIsSet;
048    
049    /**
050     * A base class for all Ant fields providing useful functionality, such as {@link Violation} management.
051     * Ideally this class would extend from {@link ProjectComponent}. However, {@link Task} objects
052     * act as fields and end up benefiting greatly from this classes functionality. Use the {@link BaseTask}
053     * functionality if the implementing class requires {@link Task} functionality, otherwise, use
054     * {@link BaseField}.
055     * Package private. Use one of the subclasses.
056     * @see BaseField
057     * @see BaseTask
058     */
059    abstract class BaseComponent extends Task
060        implements ReferenceField
061    {
062        /**
063         * Implement validate() in the base abstract class ensuring correct behavior. Provide accessors
064         * to the list of violations in protected methods.
065         * @see #appendViolation(Violation)
066         */
067        // from RequiresValidation
068        public final List<Violation> validate ()
069        {
070            validateField();
071            return _violations;
072        }
073    
074        // from ReferenceField
075        public Object getReferencedField ()
076        {
077            if (objectIsNotSet(_reference)) {
078                throw new UnsetReferenceException();
079            }
080    
081            return _reference.getReferencedObject();
082        }
083    
084        // from ReferenceField
085        public boolean isReference ()
086        {
087            return objectIsSet(_reference);
088        }
089    
090        /**
091         * A protected setter meant for concrete classes to optionally support Ant reference setting
092         * by using a public setter method which calls this, e.g. setRefid.
093         */
094        protected void setReference (Reference reference)
095        {
096            _reference = reference;
097        }
098    
099        /**
100         * Give each subclass a chance to do field specific validation.
101         */
102        protected abstract void validateField ();
103    
104        /**
105         * Add a violation to the list of violations returned when this {@link Field} is validated.
106         */
107        protected void appendViolation (Violation violation)
108        {
109            _violations.add(violation);
110        }
111    
112        /**
113         * Add a list of violations to the list of violations returned when this {@link Field} is validated.
114         */
115        protected void appendViolationList (List<Violation> violations)
116        {
117            _violations.addAll(violations);
118        }
119    
120        /**
121         * Validate a varargs list of {@link FieldWrapper} objects that are children of this {@link Field}.
122         * @see #validateChildFields(List)
123         */
124        protected ValidStatus validateChildFields (FieldWrapper<?>...childFields)
125        {
126            return validateChildFields(Arrays.asList(childFields));
127        }
128    
129        /**
130         * Validate a {@link List} of {@link FieldWrapper} objects that are children of this {@link Field}.
131         * Return a {@link ValidStatus} enum describing the valid state of the list.
132         * @throws IllegalArgumentException If the supplied {@link FieldWrapper} is null.
133         */
134        protected ValidStatus validateChildFields (List<? extends FieldWrapper<?>> childFields)
135        {
136            int invalid = 0;
137            for (final FieldWrapper<?> childField : childFields) {
138                if (objectIsNotSet(childField)) {
139                    throw new IllegalArgumentException("Programmer error. Fields being validated cannot" +
140                        "be null.");
141                }
142    
143                final int before = _violations.size();
144                appendViolationList(childField.validate());
145    
146                if (_violations.size() > before) {
147                    invalid++;
148                }
149            }
150    
151            if (invalid == childFields.size()) {
152                return ValidStatus.ALL_INVALID;
153    
154            } else if (invalid == 0) {
155                return ValidStatus.ALL_VALID;
156    
157            } else {
158                return ValidStatus.SOME_INVALID;
159            }
160        }
161    
162        /**
163         * Validate a varargs list of {@link Property} objects.
164         * @see #validateProperties(List)
165         */
166        protected ValidStatus validateProperties (Property<?>...properties)
167        {
168            return validateProperties(Arrays.asList(properties));
169        }
170    
171        /**
172         * Validate a list of {@link Property} objects.
173         * Return a {@link ValidStatus} enum describing the valid state of the list.
174         */
175        protected ValidStatus validateProperties (List<Property<?>> properties)
176        {
177            int invalid = 0;
178            for (final Property<?> property : properties) {
179                final int before = _violations.size();
180                appendViolationList(property.validate());
181    
182                if (_violations.size() > before) {
183                    invalid++;
184                }
185            }
186    
187            if (invalid == properties.size()) {
188                return ValidStatus.ALL_INVALID;
189    
190            } else if (invalid == 0) {
191                return ValidStatus.ALL_VALID;
192    
193            } else {
194                return ValidStatus.SOME_INVALID;
195            }
196        }
197    
198        /**
199         * Validate a list of {@link Property} objects that are optional. If they are unset, nothing
200         * will happen. Returns the {@link ValidStatus} of the set properties. If no properties are set,
201         * ALL_VALID is returned.
202         */
203        // TODO: can this be factored into the properties themselves, like FieldWrapper?
204        protected ValidStatus validateOptionalProperties (Property<?>...properties)
205        {
206            final List<Property<?>> setProperties = new ArrayList<Property<?>>();
207            for (final Property<?> property : properties) {
208                if (property.isNotSet()) continue;
209                setProperties.add(property);
210            }
211    
212            if (setProperties.size() == 0) {
213                return ValidStatus.ALL_VALID;
214    
215            } else {
216                return validateProperties(setProperties);
217            }
218        }
219    
220        /**
221         * If the supplied property is unset, then report a violation that the property cannot be
222         * unset if any of the supplied list of {@link Property} objects are set.
223         */
224        protected void reportUnsetDependentProperties (Property<?> property, Property<?>...dependents)
225        {
226            if (property.isNotSet()) {
227                appendViolation(new UnsetDependentPropertyViolation(property, propertyNames(dependents)));
228            }
229        }
230    
231        /**
232         * If the supplied property is set, e.g. not null, then report a violation that the property cannot
233         * be set if any of the supplied list of {@link Property} objects are set.
234         */
235        protected void reportConflictingProperties (Property<?> property, Property<?>...conflicts)
236        {
237            if (property.isSet()) {
238                appendViolation(new ConflictingPropertiesViolation(property, propertyNames(conflicts)));
239            }
240        }
241    
242        /**
243         * If the supplied {@link FieldWrapper} is unset then report a violation that the field cannot be
244         * unset if any of the supplied list of {@link FieldWrapper} objects are set.
245         */
246        protected void reportUnsetDependentFields (FieldWrapper<?> field, FieldWrapper<?>...depends)
247        {
248            if (field.isNotSet()) {
249                appendViolation(new UnsetDependentFieldViolation(field, getFieldNames(depends)));
250            }
251        }
252    
253        /**
254         * Report as a violation if the supplied {@link FieldWrapper} is unset.
255         */
256         protected void reportUnsetField (FieldWrapper<?> field)
257         {
258             if (field.isNotSet()) {
259                 appendViolation(new UnsetFieldViolation(field));
260             }
261         }
262    
263        /**
264         * Register a given {@link Field} class as a data type for this {@link Project}.
265         */
266        protected void registerField (Class<? extends Field> clazz)
267        {
268            final Project project = getProject();
269            if (objectIsNotSet(project)) {
270                throw new RuntimeException("Cannot register a field before setProject() has been called.");
271            }
272            project.addDataTypeDefinition(FieldHelper.getFieldInstance(clazz).getFieldName(), clazz);
273        }
274    
275        /**
276         * Given a list of {@link Field} Class objects, return a string representation of the field names.
277         */
278        private String getFieldNames (FieldWrapper<?>...fields)
279        {
280            final StringBuilder builder = new StringBuilder();
281            for (final FieldWrapper<?> field : fields) {
282                if (builder.length() > 0) {
283                    builder.append(", ");
284                }
285                builder.append('<').append(field.getFieldName()).append('>');
286            }
287            return builder.toString();
288        }
289    
290        /**
291         * Given a list of {@link Property} objects, return a comma separated list of the property names.
292         */
293        private String propertyNames (Property<?>...properties)
294        {
295            final StringBuilder builder = new StringBuilder();
296            for (final Property<?> prop : properties) {
297                if (builder.length() > 0) {
298                    builder.append(", ");
299                }
300                builder.append(prop.getPropertyName());
301            }
302            return builder.toString();
303        }
304    
305        /** The optional reference meant to replace this field. */
306        private Reference _reference;
307    
308        /** The list of any validation violations for this {@link Field}. */
309        private final List<Violation> _violations = new ArrayList<Violation>();
310    }