Skip to content

Package: ViewCache$ViewRecord

ViewCache$ViewRecord

nameinstructionbranchcomplexitylinemethod
ViewCache.ViewRecord(VView)
M: 0 C: 17
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 5
100%
M: 0 C: 1
100%
add(VControl, EStructuralFeature, IItemPropertyDescriptor)
M: 0 C: 12
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 2
100%
M: 0 C: 1
100%
generateID(EClass, EStructuralFeature)
M: 12 C: 12
50%
M: 1 C: 1
50%
M: 1 C: 1
50%
M: 3 C: 3
50%
M: 0 C: 1
100%
instantiate(EObject)
M: 0 C: 19
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 5
100%
M: 0 C: 1
100%

Coverage

1: /*******************************************************************************
2: * Copyright (c) 2011-2019 EclipseSource Muenchen GmbH and others.
3: *
4: * All rights reserved. This program and the accompanying materials
5: * are made available under the terms of the Eclipse Public License 2.0
6: * which accompanies this distribution, and is available at
7: * https://www.eclipse.org/legal/epl-2.0/
8: *
9: * SPDX-License-Identifier: EPL-2.0
10: *
11: * Contributors:
12: * Edgar - initial API and implementation
13: * Christian W. Damus - bug 547271
14: ******************************************************************************/
15: package org.eclipse.emf.ecp.view.model.generator;
16:
17: import java.util.HashMap;
18: import java.util.LinkedHashMap;
19: import java.util.Map;
20: import java.util.Random;
21: import java.util.function.Predicate;
22:
23: import org.eclipse.emf.ecore.EAttribute;
24: import org.eclipse.emf.ecore.EClass;
25: import org.eclipse.emf.ecore.EObject;
26: import org.eclipse.emf.ecore.EPackage;
27: import org.eclipse.emf.ecore.EReference;
28: import org.eclipse.emf.ecore.EStructuralFeature;
29: import org.eclipse.emf.ecore.util.EcoreUtil;
30: import org.eclipse.emf.ecp.view.model.common.edit.provider.CustomReflectiveItemProviderAdapterFactory;
31: import org.eclipse.emf.ecp.view.spi.model.VControl;
32: import org.eclipse.emf.ecp.view.spi.model.VDomainModelReference;
33: import org.eclipse.emf.ecp.view.spi.model.VFeaturePathDomainModelReference;
34: import org.eclipse.emf.ecp.view.spi.model.VView;
35: import org.eclipse.emf.ecp.view.spi.model.VViewFactory;
36: import org.eclipse.emf.ecp.view.spi.model.VViewPackage;
37: import org.eclipse.emf.ecp.view.spi.table.model.VTableDomainModelReference;
38: import org.eclipse.emf.ecp.view.spi.table.model.VTableFactory;
39: import org.eclipse.emf.edit.provider.AdapterFactoryItemDelegator;
40: import org.eclipse.emf.edit.provider.ComposedAdapterFactory;
41: import org.eclipse.emf.edit.provider.IItemPropertyDescriptor;
42:
43: /**
44: * A cache of the generated {@link VView} for an {@code EObject}.
45: * It is assumed that the valid features of an {@link EClass} are the same for all instances
46: * of that class and will not change during its lifetime, in keeping with standard EMF
47: * assumptions and the actual behaviour of generated models.
48: */
49: final class ViewCache {
50:
51:         private static final int CACHE_SIZE = 100;
52:         private static final Random RANDOMIZER = new Random();
53:
54:         @SuppressWarnings("serial")
55:         private final Map<EClass, ViewRecord> views = new LinkedHashMap<EClass, ViewRecord>() {
56:                 @Override
57:                 protected boolean removeEldestEntry(java.util.Map.Entry<EClass, ViewRecord> eldest) {
58:                         return size() > CACHE_SIZE;
59:                 }
60:         };
61:         private final AdapterFactoryItemDelegator delegator;
62:
63:         /**
64:          * Initializes me.
65:          */
66:         ViewCache() {
67:                 super();
68:
69:                 final ComposedAdapterFactory composedAdapterFactory = new ComposedAdapterFactory(
70:                         ComposedAdapterFactory.Descriptor.Registry.INSTANCE);
71:                 composedAdapterFactory.insertAdapterFactory(new CustomReflectiveItemProviderAdapterFactory());
72:
73:                 delegator = new AdapterFactoryItemDelegator(composedAdapterFactory);
74:         }
75:
76:         /**
77:          * Get the generated view for an {@code object}.
78:          *
79:          * @param object an object for which to get the generated mutable view model
80:          * @return the generated view model for the {@code object}
81:          */
82:         VView getView(EObject object) {
83:                 final EClass eClass = object.eClass();
84:                 ViewRecord prototype = views.get(eClass);
85:                 if (prototype == null) {
86:                         prototype = generatePrototype(eClass);
87:                         views.put(eClass, prototype);
88:                 }
89:
90:                 final VView result = prototype.instantiate(object);
91:                 return result;
92:         }
93:
94:         private ViewRecord generatePrototype(EClass eClass) {
95:                 final VView view = VViewFactory.eINSTANCE.createView();
96:                 view.setUuid(generateId(eClass, null));
97:                 final ViewRecord result = new ViewRecord(view);
98:
99:                 final EObject example = EcoreUtil.create(eClass);
100:                 final Predicate<EStructuralFeature> isValidFeature = feature -> isValidFeature(feature, example);
101:                 eClass.getEAllStructuralFeatures().stream().filter(isValidFeature)
102:                         .forEach(feature -> {
103:                                 final VControl control;
104:                                 if (isTableFeature(feature)) {
105:                                         control = VTableFactory.eINSTANCE.createTableControl();
106:                                         final VTableDomainModelReference tableDmr = VTableFactory.eINSTANCE
107:                                                 .createTableDomainModelReference();
108:                                         tableDmr.setDomainModelReference(createModelReference(feature));
109:                                         control.setDomainModelReference(tableDmr);
110:                                 } else {
111:                                         control = VViewFactory.eINSTANCE.createControl();
112:                                         control.setDomainModelReference(createModelReference(feature));
113:                                 }
114:                                 control.setUuid(result.generateID(eClass, feature));
115:                                 view.getChildren().add(control);
116:
117:                                 // If it was valid, then it had a property descriptor
118:                                 final IItemPropertyDescriptor propertyDescriptor = delegator.getPropertyDescriptor(example, feature);
119:                                 result.add(control, feature, propertyDescriptor);
120:                         });
121:
122:                 // Let the adapter factory not leak the example instance
123:                 example.eAdapters().clear();
124:
125:                 view.setRootEClass(eClass);
126:
127:                 return result;
128:         }
129:
130:         private VDomainModelReference createModelReference(final EStructuralFeature feature) {
131:                 final VFeaturePathDomainModelReference modelReference = VViewFactory.eINSTANCE
132:                         .createFeaturePathDomainModelReference();
133:                 modelReference.setDomainModelEFeature(feature);
134:                 return modelReference;
135:         }
136:
137:         private boolean isTableFeature(EStructuralFeature feature) {
138:                 if (feature instanceof EReference) {
139:                         final EReference ref = (EReference) feature;
140:                         return ref.isMany() && ref.isContainment();
141:                 }
142:                 return false;
143:         }
144:
145:         private boolean isValidFeature(EStructuralFeature feature, EObject owner) {
146:                 boolean result = !isInvalidFeature(feature);
147:
148:                 if (result) {
149:                         // Further, check that there's a property descriptor
150:                         result = delegator.getPropertyDescriptor(owner, feature) != null;
151:                 }
152:
153:                 return result;
154:         }
155:
156:         private boolean isInvalidFeature(EStructuralFeature feature) {
157:                 return isContainerReference(feature) || isTransient(feature) || isVolatile(feature);
158:         }
159:
160:         private boolean isContainerReference(EStructuralFeature feature) {
161:                 if (feature instanceof EReference) {
162:                         final EReference reference = (EReference) feature;
163:                         if (reference.isContainer()) {
164:                                 return true;
165:                         }
166:                 }
167:
168:                 return false;
169:         }
170:
171:         private boolean isTransient(EStructuralFeature feature) {
172:                 return feature.isTransient();
173:         }
174:
175:         private boolean isVolatile(EStructuralFeature feature) {
176:                 return feature.isVolatile();
177:         }
178:
179:         // this is not unique, because of the use of hashCode, so it needs to be post-processed
180:         private static String generateId(EClass eClass, EStructuralFeature feature) {
181:                 final StringBuilder stringBuilder = new StringBuilder();
182:                 final EPackage ePackage = eClass.getEPackage();
183:                 if (ePackage != null) {
184:                         /* might be null with dynamic emf */
185:                         stringBuilder.append(ePackage.getNsURI());
186:                 }
187:                 stringBuilder.append("#"); //$NON-NLS-1$
188:                 stringBuilder.append(eClass.getName());
189:                 if (feature != null) {
190:                         stringBuilder.append("#"); //$NON-NLS-1$
191:                         stringBuilder.append(feature.getName());
192:                 }
193:                 return Integer.toHexString(stringBuilder.toString().hashCode());
194:         }
195:
196:         //
197:         // Nested types
198:         //
199:
200:         /**
201:          * Internal tracking of a generated view model with metadata for calculation
202:          * of enablement of individual controls according to the EMF.Edit property
203:          * descriptor for each control as driven by a particular object in the editor.
204:          */
205:         private static final class ViewRecord {
206:                 private final Map<String, ControlRecord> controls = new HashMap<>();
207:                 private final VView view;
208:
209:                 private final ViewCopier copier = new ViewCopier();
210:
211:                 ViewRecord(VView view) {
212:                         super();
213:
214:                         this.view = view;
215:                 }
216:
217:                 /**
218:                  * Generate an ID that is guaranteed to be unique within my view.
219:                  *
220:                  * @param eClass the owner class for which to generate the ID
221:                  * @param feature the feature for which to generate the ID
222:                  *
223:                  * @return the unique generated ID
224:                  */
225:                 String generateID(EClass eClass, EStructuralFeature feature) {
226:                         String result = ViewCache.generateId(eClass, feature);
227:
228:•                        while (controls.containsKey(result)) {
229:                                 // mangle it
230:                                 int value = Integer.parseInt(result, 16);
231:                                 value = value ^ RANDOMIZER.nextInt();
232:                                 result = Integer.toHexString(value);
233:                         }
234:
235:                         return result;
236:                 }
237:
238:                 void add(VControl control, EStructuralFeature feature, IItemPropertyDescriptor propertyDescriptor) {
239:                         controls.put(control.getUuid(), new ControlRecord(feature, propertyDescriptor));
240:                 }
241:
242:                 VView instantiate(EObject object) {
243:                         copier.setOwner(object);
244:                         final VView result = (VView) copier.copy(view);
245:                         copier.copyReferences();
246:                         copier.clear();
247:                         return result;
248:                 }
249:
250:                 //
251:                 // Nested types
252:                 //
253:
254:                 /**
255:                  * A specialized copier that sets control enablement computed from
256:                  * the EMF.Edit property source for the control.
257:                  */
258:                 @SuppressWarnings("serial")
259:                 private final class ViewCopier extends EcoreUtil.Copier {
260:
261:                         private EObject owner;
262:
263:                         ViewCopier() {
264:                                 super();
265:                         }
266:
267:                         @Override
268:                         public void clear() {
269:                                 owner = null;
270:                                 super.clear();
271:                         }
272:
273:                         @Override
274:                         protected void copyAttribute(EAttribute eAttribute, EObject eObject, EObject copyEObject) {
275:                                 // Don't copy the read-only attribute; we calculate it
276:                                 if (eAttribute != VViewPackage.Literals.ELEMENT__READONLY) {
277:                                         super.copyAttribute(eAttribute, eObject, copyEObject);
278:                                 }
279:
280:                                 if (eAttribute == VViewPackage.Literals.ELEMENT__UUID) {
281:                                         // We now have the UUID, so can compute enablement override
282:                                         if (copyEObject instanceof VControl) {
283:                                                 final VControl control = (VControl) copyEObject;
284:                                                 final ControlRecord record = controls.get(control.getUuid());
285:                                                 if (record != null) {
286:                                                         control.setReadonly(!record.isEditable(owner));
287:                                                 }
288:                                         }
289:                                 }
290:                         }
291:
292:                         /**
293:                          * Set the object for which we are copying the view model, to edit it.
294:                          *
295:                          * @param owner the owner object of the features to be edited
296:                          */
297:                         void setOwner(EObject owner) {
298:                                 this.owner = owner;
299:                         }
300:                 }
301:
302:         }
303:
304:         /**
305:          * A record tracking metadata about the structural feature edited by a control,
306:          * in particular for determination of enablement according to its EMF.Edit item
307:          * provider.
308:          */
309:         private static final class ControlRecord {
310:                 private final IItemPropertyDescriptor propertyDescriptor;
311:                 private final boolean changeable;
312:
313:                 ControlRecord(EStructuralFeature feature, IItemPropertyDescriptor propertyDescriptor) {
314:                         super();
315:
316:                         this.propertyDescriptor = propertyDescriptor;
317:                         changeable = feature.isChangeable();
318:                 }
319:
320:                 boolean isEditable(EObject owner) {
321:                         return changeable && propertyDescriptor.canSetProperty(owner);
322:                 }
323:         }
324:
325: }