View Javadoc
1   /*
2    * Portions of this software was developed by employees of the National Institute
3    * of Standards and Technology (NIST), an agency of the Federal Government and is
4    * being made available as a public service. Pursuant to title 17 United States
5    * Code Section 105, works of NIST employees are not subject to copyright
6    * protection in the United States. This software may be subject to foreign
7    * copyright. Permission in the United States and in foreign countries, to the
8    * extent that NIST may hold copyright, to use, copy, modify, create derivative
9    * works, and distribute this software and its documentation without fee is hereby
10   * granted on a non-exclusive basis, provided that this notice and disclaimer
11   * of warranty appears in all copies.
12   *
13   * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
14   * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
15   * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
16   * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
17   * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
18   * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE.  IN NO EVENT
19   * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
20   * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
21   * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
22   * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
23   * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
24   * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
25   */
26  
27  package gov.nist.secauto.oscal.lib.profile.resolver.alter;
28  
29  import gov.nist.secauto.metaschema.model.common.datatype.markup.MarkupLine;
30  import gov.nist.secauto.metaschema.model.common.util.CollectionUtil;
31  import gov.nist.secauto.metaschema.model.common.util.CustomCollectors;
32  import gov.nist.secauto.metaschema.model.common.util.ObjectUtils;
33  import gov.nist.secauto.oscal.lib.model.Catalog;
34  import gov.nist.secauto.oscal.lib.model.CatalogGroup;
35  import gov.nist.secauto.oscal.lib.model.Control;
36  import gov.nist.secauto.oscal.lib.model.ControlPart;
37  import gov.nist.secauto.oscal.lib.model.Link;
38  import gov.nist.secauto.oscal.lib.model.Parameter;
39  import gov.nist.secauto.oscal.lib.model.Property;
40  import gov.nist.secauto.oscal.lib.model.control.catalog.ICatalogVisitor;
41  import gov.nist.secauto.oscal.lib.profile.resolver.ProfileResolutionEvaluationException;
42  
43  import java.util.Collections;
44  import java.util.EnumMap;
45  import java.util.EnumSet;
46  import java.util.LinkedList;
47  import java.util.List;
48  import java.util.ListIterator;
49  import java.util.Locale;
50  import java.util.Map;
51  import java.util.Set;
52  import java.util.concurrent.ConcurrentHashMap;
53  import java.util.function.Consumer;
54  import java.util.function.Function;
55  import java.util.function.Supplier;
56  
57  import edu.umd.cs.findbugs.annotations.NonNull;
58  import edu.umd.cs.findbugs.annotations.Nullable;
59  
60  public class AddVisitor implements ICatalogVisitor<Boolean, AddVisitor.Context> {
61    public enum TargetType {
62      CONTROL("control", Control.class),
63      PARAM("param", Parameter.class),
64      PART("part", ControlPart.class);
65  
66      @NonNull
67      private static final Map<Class<?>, TargetType> CLASS_TO_TYPE;
68      @NonNull
69      private static final Map<String, TargetType> NAME_TO_TYPE;
70      @NonNull
71      private final String fieldName;
72      @NonNull
73      private final Class<?> clazz;
74  
75      static {
76        {
77          Map<Class<?>, TargetType> map = new ConcurrentHashMap<>();
78          for (TargetType type : TargetType.values()) {
79            map.put(type.getClazz(), type);
80          }
81          CLASS_TO_TYPE = CollectionUtil.unmodifiableMap(map);
82        }
83  
84        {
85          Map<String, TargetType> map = new ConcurrentHashMap<>();
86          for (TargetType type : TargetType.values()) {
87            map.put(type.fieldName(), type);
88          }
89          NAME_TO_TYPE = CollectionUtil.unmodifiableMap(map);
90        }
91      }
92  
93      /**
94       * Get the target type associated with the provided {@code clazz}.
95       *
96       * @param clazz
97       *          the class to identify the target type for
98       * @return the associated target type or {@code null} if the class is not
99       *         associated with a target type
100      */
101     @Nullable
102     public static TargetType forClass(@NonNull Class<?> clazz) {
103       Class<?> target = clazz;
104       TargetType retval;
105       // recurse over parent classes to find a match
106       do {
107         retval = CLASS_TO_TYPE.get(target);
108       } while (retval == null && (target = target.getSuperclass()) != null);
109       return retval;
110     }
111 
112     /**
113      * Get the target type associated with the provided field {@code name}.
114      *
115      * @param name
116      *          the field name to identify the target type for
117      * @return the associated target type or {@code null} if the name is not
118      *         associated with a target type
119      */
120     @Nullable
121     public static TargetType forFieldName(@Nullable String name) {
122       return name == null ? null : NAME_TO_TYPE.get(name);
123     }
124 
125     TargetType(@NonNull String fieldName, @NonNull Class<?> clazz) {
126       this.fieldName = fieldName;
127       this.clazz = clazz;
128     }
129 
130     /**
131      * Get the field name associated with the target type.
132      *
133      * @return the name
134      */
135     public String fieldName() {
136       return fieldName;
137     }
138 
139     /**
140      * Get the bound class associated with the target type.
141      *
142      * @return the class
143      */
144     public Class<?> getClazz() {
145       return clazz;
146     }
147   }
148 
149   public enum Position {
150     BEFORE,
151     AFTER,
152     STARTING,
153     ENDING;
154 
155     @NonNull
156     private static final Map<String, Position> NAME_TO_POSITION;
157 
158     static {
159       Map<String, Position> map = new ConcurrentHashMap<>();
160       for (Position position : Position.values()) {
161         map.put(position.name().toLowerCase(Locale.ROOT), position);
162       }
163       NAME_TO_POSITION = CollectionUtil.unmodifiableMap(map);
164     }
165 
166     /**
167      * Get the position associated with the provided {@code name}.
168      *
169      * @param name
170      *          the name to identify the position for
171      * @return the associated position or {@code null} if the name is not associated
172      *         with a position
173      */
174     @Nullable
175     public static Position forName(@Nullable String name) {
176       return name == null ? null : NAME_TO_POSITION.get(name);
177     }
178   }
179 
180   @NonNull
181   private static final AddVisitor INSTANCE = new AddVisitor();
182   private static final Map<TargetType, Set<TargetType>> APPLICABLE_TARGETS;
183 
184   static {
185     APPLICABLE_TARGETS = new EnumMap<>(TargetType.class);
186     APPLICABLE_TARGETS.put(TargetType.CONTROL, Set.of(TargetType.CONTROL, TargetType.PARAM, TargetType.PART));
187     APPLICABLE_TARGETS.put(TargetType.PARAM, Set.of(TargetType.PARAM));
188     APPLICABLE_TARGETS.put(TargetType.PART, Set.of(TargetType.PART));
189   }
190 
191   private static Set<TargetType> getApplicableTypes(@NonNull TargetType type) {
192     return APPLICABLE_TARGETS.getOrDefault(type, CollectionUtil.emptySet());
193   }
194 
195   /**
196    * Apply the add directive.
197    *
198    * @param control
199    *          the control target
200    * @param position
201    *          the position to apply the content or {@code null}
202    * @param byId
203    *          the identifier of the target or {@code null}
204    * @param title
205    *          a title to set
206    * @param params
207    *          parameters to add
208    * @param props
209    *          properties to add
210    * @param links
211    *          links to add
212    * @param parts
213    *          parts to add
214    * @return {@code true} if the modification was made or {@code false} otherwise
215    * @throws ProfileResolutionEvaluationException
216    *           if a processing error occurred during profile resolution
217    */
218   public static boolean add(
219       @NonNull Control control,
220       @Nullable Position position,
221       @Nullable String byId,
222       @Nullable MarkupLine title,
223       @NonNull List<Parameter> params,
224       @NonNull List<Property> props,
225       @NonNull List<Link> links,
226       @NonNull List<ControlPart> parts) {
227     return INSTANCE.visitControl(
228         control,
229         new Context(
230             control,
231             position == null ? Position.ENDING : position,
232             byId,
233             title,
234             params,
235             props,
236             links,
237             parts));
238   }
239 
240   @Override
241   public Boolean visitCatalog(Catalog catalog, Context context) {
242     // not required
243     throw new UnsupportedOperationException("not needed");
244   }
245 
246   @Override
247   public Boolean visitGroup(CatalogGroup group, Context context) {
248     // not required
249     throw new UnsupportedOperationException("not needed");
250   }
251 
252   /**
253    * If the add applies to the current object, then apply the child objects.
254    * <p>
255    * An add applies if:
256    * <ol>
257    * <li>the {@code targetItem} supports all of the children</li>
258    * <li>the context matches if:
259    * <ul>
260    * <li>the target item's id matches the "by-id"; or</li>
261    * <li>the "by-id" is not defined and the target item is the control matching
262    * the target context</li>
263    * </ul>
264    * </li>
265    * </ol>
266    *
267    * @param <T>
268    *          the type of the {@code targetItem}
269    * @param targetItem
270    *          the current target to process
271    * @param titleConsumer
272    *          a consumer to apply a title to or {@code null} if the object has no
273    *          title field
274    * @param paramsSupplier
275    *          a supplier for the child {@link Parameter} collection
276    * @param propsSupplier
277    *          a supplier for the child {@link Property} collection
278    * @param linksSupplier
279    *          a supplier for the child {@link Link} collection
280    * @param partsSupplier
281    *          a supplier for the child {@link ControlPart} collection
282    * @param context
283    *          the add context
284    * @return {@code true} if a modification was made or {@code false} otherwise
285    */
286   private static <T> boolean handleCurrent(
287       @NonNull T targetItem,
288       @Nullable Consumer<MarkupLine> titleConsumer,
289       @Nullable Supplier<? extends List<Parameter>> paramsSupplier,
290       @Nullable Supplier<? extends List<Property>> propsSupplier,
291       @Nullable Supplier<? extends List<Link>> linksSupplier,
292       @Nullable Supplier<? extends List<ControlPart>> partsSupplier,
293       @NonNull Context context) {
294     boolean retval = false;
295     Position position = context.getPosition();
296     if (context.appliesTo(targetItem) && !context.isSequenceTargeted(targetItem)) {
297       // the target item is the target of the add
298       MarkupLine newTitle = context.getTitle();
299       if (newTitle != null) {
300         assert titleConsumer != null;
301         titleConsumer.accept(newTitle);
302       }
303 
304       handleCollection(position, context.getParams(), paramsSupplier);
305       handleCollection(position, context.getProps(), propsSupplier);
306       handleCollection(position, context.getLinks(), linksSupplier);
307       handleCollection(position, context.getParts(), partsSupplier);
308       retval = true;
309     }
310     return retval;
311   }
312 
313   private static <T> void handleCollection(
314       @NonNull Position position,
315       @NonNull List<T> newItems,
316       @Nullable Supplier<? extends List<T>> originalCollectionSupplier) {
317     if (originalCollectionSupplier != null) {
318       List<T> oldItems = originalCollectionSupplier.get();
319       if (!newItems.isEmpty()) {
320         if (Position.STARTING.equals(position)) {
321           oldItems.addAll(0, newItems);
322         } else { // ENDING
323           oldItems.addAll(newItems);
324         }
325       }
326     }
327   }
328 
329   // private static <T> void handleChild(
330   // @NonNull TargetType itemType,
331   // @NonNull Supplier<? extends List<T>> collectionSupplier,
332   // @Nullable Consumer<T> handler,
333   // @NonNull Context context) {
334   // boolean handleChildren = !Collections.disjoint(context.getTargetItemTypes(),
335   // getApplicableTypes(itemType));
336   // if (handleChildren && handler != null) {
337   // // if the child item type is applicable and there is a handler, iterate over
338   // children
339   // Iterator<T> iter = collectionSupplier.get().iterator();
340   // while (iter.hasNext()) {
341   // T item = iter.next();
342   // if (item != null) {
343   // handler.accept(item);
344   // }
345   // }
346   // }
347   // }
348 
349   private static <T> boolean handleChild(
350       @NonNull TargetType itemType,
351       @NonNull Supplier<? extends List<T>> originalCollectionSupplier,
352       @NonNull Supplier<? extends List<T>> newItemsSupplier,
353       @Nullable Function<T, Boolean> handler,
354       @NonNull Context context) {
355 
356     // determine if this child type can match
357     boolean isItemTypeMatch = context.isMatchingType(itemType);
358 
359     Set<TargetType> applicableTypes = getApplicableTypes(itemType);
360     boolean descendChild = handler != null && !Collections.disjoint(context.getTargetItemTypes(), applicableTypes);
361 
362     boolean retval = false;
363     if (isItemTypeMatch || descendChild) {
364       // if the item type is applicable, attempt to match by id
365       List<T> collection = originalCollectionSupplier.get();
366       ListIterator<T> iter = collection.listIterator();
367       boolean deferred = false;
368       while (iter.hasNext()) {
369         T item = ObjectUtils.requireNonNull(iter.next());
370 
371         if (isItemTypeMatch && context.appliesTo(item) && context.isSequenceTargeted(item)) {
372           // if id match, inject the new items into the collection
373           switch (context.getPosition()) {
374           case AFTER: {
375             newItemsSupplier.get().forEach(add -> iter.add(add));
376             retval = true;
377             break;
378           }
379           case BEFORE: {
380             iter.previous();
381             List<T> adds = newItemsSupplier.get();
382             adds.forEach(add -> iter.add(add));
383             item = iter.next();
384             retval = true;
385             break;
386           }
387           case STARTING:
388           case ENDING:
389             deferred = true;
390             break;
391           default:
392             throw new UnsupportedOperationException(context.getPosition().name().toLowerCase(Locale.ROOT));
393           }
394         }
395 
396         if (descendChild) {
397           assert handler != null;
398 
399           // handle child items since they are applicable to the search criteria
400           retval = retval || handler.apply(item);
401         }
402       }
403 
404       if (deferred) {
405         List<T> newItems = newItemsSupplier.get();
406         if (Position.ENDING.equals(context.getPosition())) {
407           collection.addAll(newItems);
408           retval = true;
409         } else if (Position.STARTING.equals(context.getPosition())) {
410           collection.addAll(0, newItems);
411           retval = true;
412         }
413       }
414     }
415     return retval;
416   }
417 
418   @Override
419   public Boolean visitControl(Control control, Context context) {
420     assert context != null;
421 
422     if (control.getParams() == null) {
423       control.setParams(new LinkedList<>());
424     }
425 
426     if (control.getProps() == null) {
427       control.setProps(new LinkedList<>());
428     }
429 
430     if (control.getLinks() == null) {
431       control.setLinks(new LinkedList<>());
432     }
433 
434     if (control.getParts() == null) {
435       control.setParts(new LinkedList<>());
436     }
437 
438     boolean retval = handleCurrent(
439         control,
440         title -> control.setTitle(title),
441         () -> control.getParams(),
442         () -> control.getProps(),
443         () -> control.getLinks(),
444         () -> control.getParts(),
445         context);
446 
447     // visit params
448     retval = retval || handleChild(
449         TargetType.PARAM,
450         () -> control.getParams(),
451         () -> context.getParams(),
452         child -> visitParameter(ObjectUtils.notNull(child), context),
453         context);
454 
455     // visit parts
456     retval = retval || handleChild(
457         TargetType.PART,
458         () -> control.getParts(),
459         () -> context.getParts(),
460         child -> visitPart(child, context),
461         context);
462 
463     // visit control children
464     for (Control childControl : CollectionUtil.listOrEmpty(control.getControls())) {
465       Set<TargetType> applicableTypes = getApplicableTypes(TargetType.CONTROL);
466       if (!Collections.disjoint(context.getTargetItemTypes(), applicableTypes)) {
467         retval = retval || visitControl(ObjectUtils.requireNonNull(childControl), context);
468       }
469     }
470     return retval;
471   }
472 
473   @Override
474   public Boolean visitParameter(Parameter parameter, Context context) {
475     assert context != null;
476     if (parameter.getProps() == null) {
477       parameter.setProps(new LinkedList<>());
478     }
479 
480     if (parameter.getLinks() == null) {
481       parameter.setLinks(new LinkedList<>());
482     }
483 
484     return handleCurrent(
485         parameter,
486         null,
487         null,
488         () -> parameter.getProps(),
489         () -> parameter.getLinks(),
490         null,
491         context);
492   }
493 
494   /**
495    * Visit the control part.
496    *
497    * @param part
498    *          the bound part object
499    * @param context
500    *          the visitor context
501    * @return {@code true} if the removal was applied or {@code false} otherwise
502    */
503   public boolean visitPart(ControlPart part, Context context) {
504     assert context != null;
505     if (part.getProps() == null) {
506       part.setProps(new LinkedList<>());
507     }
508 
509     if (part.getLinks() == null) {
510       part.setLinks(new LinkedList<>());
511     }
512 
513     if (part.getParts() == null) {
514       part.setParts(new LinkedList<>());
515     }
516 
517     boolean retval = handleCurrent(
518         part,
519         null,
520         null,
521         () -> part.getProps(),
522         () -> part.getLinks(),
523         () -> part.getParts(),
524         context);
525 
526     // visit parts
527     retval = retval || handleChild(
528         TargetType.PART,
529         () -> part.getParts(),
530         () -> context.getParts(),
531         child -> visitPart(child, context),
532         context);
533     return retval;
534   }
535 
536   static class Context {
537     @NonNull
538     private static final Set<TargetType> TITLE_TYPES = ObjectUtils.notNull(
539         Set.of(TargetType.CONTROL, TargetType.PART));
540     @NonNull
541     private static final Set<TargetType> PARAM_TYPES = ObjectUtils.notNull(
542         Set.of(TargetType.CONTROL, TargetType.PARAM));
543     @NonNull
544     private static final Set<TargetType> PROP_TYPES = ObjectUtils.notNull(
545         Set.of(TargetType.CONTROL, TargetType.PARAM, TargetType.PART));
546     @NonNull
547     private static final Set<TargetType> LINK_TYPES = ObjectUtils.notNull(
548         Set.of(TargetType.CONTROL, TargetType.PARAM, TargetType.PART));
549     @NonNull
550     private static final Set<TargetType> PART_TYPES = ObjectUtils.notNull(
551         Set.of(TargetType.CONTROL, TargetType.PART));
552 
553     @NonNull
554     private final Control control;
555     @NonNull
556     private final Position position;
557     @Nullable
558     private final String byId;
559     @Nullable
560     private final MarkupLine title;
561     @NonNull
562     private final List<Parameter> params;
563     @NonNull
564     private final List<Property> props;
565     @NonNull
566     private final List<Link> links;
567     @NonNull
568     private final List<ControlPart> parts;
569     @NonNull
570     private final Set<TargetType> targetItemTypes;
571 
572     public Context(
573         @NonNull Control control,
574         @NonNull Position position,
575         @Nullable String byId,
576         @Nullable MarkupLine title,
577         @NonNull List<Parameter> params,
578         @NonNull List<Property> props,
579         @NonNull List<Link> links,
580         @NonNull List<ControlPart> parts) {
581 
582       Set<TargetType> targetItemTypes = ObjectUtils.notNull(EnumSet.allOf(TargetType.class));
583 
584       List<String> additionObjects = new LinkedList<>();
585 
586       boolean sequenceTarget = true;
587       if (title != null) {
588         targetItemTypes.retainAll(TITLE_TYPES);
589         additionObjects.add("title");
590         sequenceTarget = false;
591       }
592 
593       if (!params.isEmpty()) {
594         targetItemTypes.retainAll(PARAM_TYPES);
595         additionObjects.add("param");
596       }
597 
598       if (!props.isEmpty()) {
599         targetItemTypes.retainAll(PROP_TYPES);
600         additionObjects.add("prop");
601         sequenceTarget = false;
602       }
603 
604       if (!links.isEmpty()) {
605         targetItemTypes.retainAll(LINK_TYPES);
606         additionObjects.add("link");
607         sequenceTarget = false;
608       }
609 
610       if (!parts.isEmpty()) {
611         targetItemTypes.retainAll(PART_TYPES);
612         additionObjects.add("part");
613       }
614 
615       if (Position.BEFORE.equals(position) || Position.AFTER.equals(position)) {
616         if (sequenceTarget) {
617           if (sequenceTarget && !params.isEmpty() && parts.isEmpty()) {
618             targetItemTypes.retainAll(Set.of(TargetType.PARAM));
619           } else if (sequenceTarget && !parts.isEmpty() && params.isEmpty()) {
620             targetItemTypes.retainAll(Set.of(TargetType.PART));
621           } else {
622             throw new ProfileResolutionEvaluationException(
623                 "When using position before or after, only one collection of parameters or parts can be specified.");
624           }
625         } else {
626           throw new ProfileResolutionEvaluationException(
627               "When using position before or after, one collection of parameters or parts can be specified."
628                   + " Other additions must not be used.");
629         }
630       }
631 
632       if (targetItemTypes.isEmpty()) {
633         throw new ProfileResolutionEvaluationException("No parent object supports the requested objects to add: " +
634             additionObjects.stream().collect(CustomCollectors.joiningWithOxfordComma("or")));
635       }
636 
637       this.control = control;
638       this.position = position;
639       this.byId = byId;
640       this.title = title;
641       this.params = params;
642       this.props = props;
643       this.links = links;
644       this.parts = parts;
645       this.targetItemTypes = CollectionUtil.unmodifiableSet(targetItemTypes);
646     }
647 
648     public Control getControl() {
649       return control;
650     }
651 
652     @NonNull
653     public Position getPosition() {
654       return position;
655     }
656 
657     @Nullable
658     public String getById() {
659       return byId;
660     }
661 
662     @Nullable
663     public MarkupLine getTitle() {
664       return title;
665     }
666 
667     @NonNull
668     public List<Parameter> getParams() {
669       return params;
670     }
671 
672     @NonNull
673     public List<Property> getProps() {
674       return props;
675     }
676 
677     @NonNull
678     public List<Link> getLinks() {
679       return links;
680     }
681 
682     @NonNull
683     public List<ControlPart> getParts() {
684       return parts;
685     }
686 
687     @NonNull
688     public Set<TargetType> getTargetItemTypes() {
689       return targetItemTypes;
690     }
691 
692     public boolean isMatchingType(@NonNull TargetType type) {
693       return getTargetItemTypes().contains(type);
694     }
695 
696     public <T> boolean isSequenceTargeted(T targetItem) {
697       TargetType objectType = TargetType.forClass(targetItem.getClass());
698       return (Position.BEFORE.equals(position) || Position.AFTER.equals(position))
699           && (TargetType.PARAM.equals(objectType) && isMatchingType(TargetType.PARAM)
700               || TargetType.PART.equals(objectType) && isMatchingType(TargetType.PART));
701     }
702 
703     protected boolean checkValue(@Nullable String actual, @Nullable String expected) {
704       return expected == null || expected.equals(actual);
705     }
706 
707     /**
708      * Determine if the provided {@code obj} is the target of the add.
709      *
710      * @param obj
711      *          the current object
712      * @return {@code true} if the current object applies or {@code false} otherwise
713      */
714     public boolean appliesTo(@NonNull Object obj) {
715       TargetType objectType = TargetType.forClass(obj.getClass());
716 
717       boolean retval = objectType != null && isMatchingType(objectType);
718       if (retval) {
719         assert objectType != null;
720 
721         // check other criteria
722         String actualId = null;
723 
724         switch (objectType) {
725         case CONTROL: {
726           Control control = (Control) obj;
727           actualId = control.getId();
728           break;
729         }
730         case PARAM: {
731           Parameter param = (Parameter) obj;
732           actualId = param.getId();
733           break;
734         }
735         case PART: {
736           ControlPart part = (ControlPart) obj;
737           actualId = part.getId() == null ? null : part.getId().toString();
738           break;
739         }
740         default:
741           throw new UnsupportedOperationException(objectType.fieldName());
742         }
743 
744         String byId = getById();
745         if (getById() == null && TargetType.CONTROL.equals(objectType)) {
746           retval = getControl().equals(obj);
747         } else {
748           retval = byId != null && byId.equals(actualId);
749         }
750       }
751       return retval;
752     }
753   }
754 }