001/* 002 * Copyright 2018 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.runtime.model; 017 018import java.util.ArrayDeque; 019import java.util.Arrays; 020import java.util.Collection; 021import java.util.Deque; 022import java.util.HashMap; 023import java.util.Map; 024import java.util.Optional; 025import java.util.Set; 026import java.util.function.BiFunction; 027import java.util.stream.Collectors; 028 029import org.apache.commons.lang3.StringUtils; 030 031import org.ametys.runtime.model.exception.BadItemTypeException; 032import org.ametys.runtime.model.exception.UndefinedItemPathException; 033 034/** 035 * Helper class for views 036 */ 037public final class ViewHelper 038{ 039 private ViewHelper() 040 { 041 // Empty constructor 042 } 043 044 /** 045 * Add the items of the view item container to include if they are not already present in the current view item container or in the reference view 046 * @param currentContainer the current container 047 * @param containerToInclude the container to include 048 * @param referenceView the reference view 049 */ 050 public static void addViewContainerItems(ViewItemContainer currentContainer, ViewItemContainer containerToInclude, View referenceView) 051 { 052 for (ViewItem itemToCopy : containerToInclude.getViewItems()) 053 { 054 if (itemToCopy instanceof SimpleViewItemGroup) 055 { 056 SimpleViewItemGroup groupToCopy = (SimpleViewItemGroup) itemToCopy; 057 SimpleViewItemGroup group = new SimpleViewItemGroup(); 058 group.copyGroupItem(groupToCopy, referenceView); 059 060 currentContainer.addViewItem(group); 061 } 062 else 063 { 064 String itemName = itemToCopy.getName(); 065 if (!currentContainer.hasModelViewItem(itemName) && !referenceView.hasModelViewItem(itemName)) 066 { 067 currentContainer.addViewItem(itemToCopy); 068 } 069 } 070 } 071 } 072 073 /** 074 * Creates a {@link ViewItemContainer} with the items of the given {@link ModelItemContainer} 075 * @param <T> Type of the {@link ViewItemContainer} to create ({@link View} or {@link ModelViewItemGroup}) 076 * @param modelItemContainers the model item containers 077 * @return the created {@link ViewItemContainer} 078 * @throws IllegalArgumentException if the model item containers collection is empty 079 */ 080 public static <T extends ViewItemContainer> T createViewItemContainer(Collection<? extends ModelItemContainer> modelItemContainers) throws IllegalArgumentException 081 { 082 T viewItemContainer = createEmptyViewItemContainer(modelItemContainers); 083 084 for (ModelItemContainer modelItemContainer : modelItemContainers) 085 { 086 for (ModelItem modelItem : modelItemContainer.getModelItems()) 087 { 088 addViewItem(modelItem.getName(), viewItemContainer, modelItemContainer); 089 } 090 } 091 092 return viewItemContainer; 093 } 094 095 /** 096 * Creates a {@link ViewItemContainer} with the given items 097 * @param <T> Type of the {@link ViewItemContainer} to create ({@link View} or {@link ModelViewItemGroup}) 098 * @param modelItemContainers the model items containing items definitions 099 * @param itemPaths the paths of the items to put in the view item container 100 * @return the created {@link ViewItemContainer} 101 * @throws IllegalArgumentException if the model item containers collection is empty or if an item path is <code>null</code>, empty, or is not defined in the given model item containers 102 * @throws BadItemTypeException if a segment in a path (but not the last) does not represent a group item 103 */ 104 public static <T extends ViewItemContainer> T createViewItemContainer(Collection<? extends ModelItemContainer> modelItemContainers, String... itemPaths) throws IllegalArgumentException, BadItemTypeException 105 { 106 T viewItemContainer = createEmptyViewItemContainer(modelItemContainers); 107 108 for (String itemPath : itemPaths) 109 { 110 if (ModelHelper.hasModelItem(itemPath, modelItemContainers)) 111 { 112 addViewItem(itemPath, viewItemContainer, modelItemContainers.toArray(new ModelItemContainer[modelItemContainers.size()])); 113 } 114 else 115 { 116 String modelIds = StringUtils.join(modelItemContainers.stream() 117 .map(modelItemContainer -> _getModelItemContainerIdentifier(modelItemContainer)) 118 .collect(Collectors.toList()), ", "); 119 throw new IllegalArgumentException("Item '" + itemPath + "' not found in models: '" + modelIds + "'."); 120 } 121 } 122 123 return viewItemContainer; 124 } 125 126 /** 127 * Creates an empty {@link ViewItemContainer} 128 * The created container can be a {@link View} or a {@link ModelViewItemGroup}, according to the given {@link ModelItemContainer}s. 129 * @param <T> The type of the created container 130 * @param modelItemContainers the model items containing items definitions 131 * @return the created {@link ViewItemContainer} 132 * @throws IllegalArgumentException if the model item containers collection is empty 133 */ 134 @SuppressWarnings("unchecked") 135 public static <T extends ViewItemContainer> T createEmptyViewItemContainer(Collection<? extends ModelItemContainer> modelItemContainers) throws IllegalArgumentException 136 { 137 if (modelItemContainers.isEmpty()) 138 { 139 throw new IllegalArgumentException("The model is needed to create a view item container"); 140 } 141 else 142 { 143 ModelItemContainer firstModelItemContainer = modelItemContainers.iterator().next(); 144 if (firstModelItemContainer instanceof ModelItemGroup) 145 { 146 ModelViewItemGroup viewItemContainer = new ModelViewItemGroup(); 147 viewItemContainer.setDefinition((ModelItemGroup) firstModelItemContainer); 148 return (T) viewItemContainer; 149 } 150 else 151 { 152 return (T) new View(); 153 } 154 } 155 } 156 157 /** 158 * Add a view item in the given container 159 * @param relativePath path of the item to add 160 * @param viewItemContainer the view item container 161 * @param modelItemContainers the corresponding model item containers 162 * @throws IllegalArgumentException if the path is <code>null</code> or empty 163 * @throws UndefinedItemPathException if the path is not defined in the given model item containers 164 * @throws BadItemTypeException if a segment in a path (but not the last) does not represent a group item 165 */ 166 @SuppressWarnings("unchecked") 167 public static void addViewItem(String relativePath, ViewItemContainer viewItemContainer, ModelItemContainer... modelItemContainers) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException 168 { 169 int firstIndexOfItemPathSeparator = relativePath.indexOf(ModelItem.ITEM_PATH_SEPARATOR); 170 String fisrtPathSegment = firstIndexOfItemPathSeparator > -1 ? relativePath.substring(0, relativePath.indexOf(ModelItem.ITEM_PATH_SEPARATOR)) : relativePath; 171 172 ModelItem modelItem = ModelHelper.getModelItem(fisrtPathSegment, Arrays.asList(modelItemContainers)); 173 174 // Find the view item in the current view item container 175 ModelViewItem viewItem = viewItemContainer.getModelViewItem(fisrtPathSegment); 176 if (viewItem == null) 177 { 178 // If the view item does not exist, then create it... 179 viewItem = modelItem instanceof ModelItemGroup ? new ModelViewItemGroup() : new ViewElement(); 180 viewItem.setDefinition(modelItem); 181 182 // ... and add it to the current view item container 183 viewItemContainer.addViewItem(viewItem); 184 } 185 186 if (viewItem instanceof ViewItemContainer) 187 { 188 // If the view item is a container, the model item is a container too 189 ModelItemContainer newModelItemContainer = (ModelItemContainer) modelItem; 190 191 if (firstIndexOfItemPathSeparator > -1) 192 { 193 // Only the first segment of the path has been processed, now recursively process the next ones 194 String subPath = relativePath.substring(firstIndexOfItemPathSeparator + 1); 195 addViewItem(subPath, (ViewItemContainer) viewItem, newModelItemContainer); 196 } 197 else 198 { 199 // The last segment is a group, add all its children in the current view item container 200 for (ModelItem child : newModelItemContainer.getModelItems()) 201 { 202 String newRelativePath = StringUtils.removeStart(child.getPath(), modelItem.getPath() + ModelItem.ITEM_PATH_SEPARATOR); 203 addViewItem(newRelativePath, (ViewItemContainer) viewItem, newModelItemContainer); 204 } 205 } 206 } 207 else if (firstIndexOfItemPathSeparator > -1) 208 { 209 // The path has several segments but the first one is not a view item container 210 throw new BadItemTypeException("The segments inside the items' paths can only refer to a group. The path '" + relativePath + "' refers to the item '" + modelItem.getPath() + "' that is not a group"); 211 } 212 } 213 214 private static Optional<String> _getModelItemContainerIdentifier(ModelItemContainer container) 215 { 216 if (container instanceof Model) 217 { 218 return Optional.ofNullable(((Model) container).getId()); 219 } 220 else if (container instanceof ModelItem) 221 { 222 return Optional.ofNullable(((ModelItem) container).getName()); 223 } 224 else 225 { 226 return Optional.empty(); 227 } 228 } 229 230 /** 231 * Retrieves the paths of all model items in the given {@link View} 232 * @param view the {@link View} 233 * @return the paths of all items 234 */ 235 public static Set<String> getModelItemsPathsFromView(View view) 236 { 237 return _getModelItemsFromViewItemContainer(view).keySet(); 238 } 239 240 /** 241 * Retrieves all model items in the given {@link View} 242 * @param view the {@link View} 243 * @return all model items 244 */ 245 public static Collection<ModelItem> getModelItemsFromView(View view) 246 { 247 return _getModelItemsFromViewItemContainer(view).values(); 248 } 249 250 /** 251 * Retrieves all model items in the given {@link ViewItemContainer}, indexed by their paths 252 * @param viewItemContainer the {@link ViewItemContainer} 253 * @return all model items 254 */ 255 private static Map<String, ModelItem> _getModelItemsFromViewItemContainer(ViewItemContainer viewItemContainer) 256 { 257 Map<String, ModelItem> result = new HashMap<>(); 258 259 for (ViewItem viewItem : viewItemContainer.getViewItems()) 260 { 261 if (viewItem instanceof ModelViewItem) 262 { 263 ModelItem modelItem = ((ModelViewItem) viewItem).getDefinition(); 264 result.put(modelItem.getPath(), modelItem); 265 } 266 267 if (viewItem instanceof ViewItemContainer) 268 { 269 result.putAll(_getModelItemsFromViewItemContainer((ViewItemContainer) viewItem)); 270 } 271 } 272 273 return result; 274 } 275 276 /** 277 * Gets the {@link ModelViewItem} from the {@link ViewItemContainer} at the given path. 278 * <br>Unlike {@link View#getModelViewItem(String)}, this method accepts a path and not only a name, allowing to traverse composites. 279 * @param itemContainer The container of view items 280 * @param itemPath The path of the item to get 281 * @return The {@link ModelViewItem}. Can never be <code>null</code> 282 * @throws UndefinedItemPathException If one of the parts of the given path is undefined 283 * @throws BadItemTypeException If one of the parts of the given path is defined, but is not of the correct type 284 */ 285 public static ModelViewItem getModelViewItem(ViewItemContainer itemContainer, String itemPath) throws UndefinedItemPathException, BadItemTypeException 286 { 287 return new ViewItemGetter(itemContainer).getViewItem(itemPath); 288 } 289 290 /** 291 * Gets the {@link ViewElement} from the {@link ViewItemContainer} at the given path. 292 * <br>Unlike {@link View#getModelViewItem(String)}, this method accepts a path and not only a name, allowing to traverse composites. 293 * @param itemContainer The container of view items 294 * @param itemPath The path of the item to get 295 * @return The {@link ViewElement}. Can never be <code>null</code> 296 * @throws UndefinedItemPathException If one of the parts of the given path is undefined 297 * @throws BadItemTypeException If one of the parts of the given path is defined, but is not of the correct type 298 */ 299 public static ViewElement getViewElement(ViewItemContainer itemContainer, String itemPath) throws UndefinedItemPathException, BadItemTypeException 300 { 301 return new ViewItemGetter(itemContainer).getModelViewItem(itemPath); 302 } 303 304 /** 305 * Gets the {@link ModelViewItemGroup} from the {@link ViewItemContainer} at the given path. 306 * <br>Unlike {@link View#getModelViewItem(String)}, this method accepts a path and not only a name, allowing to traverse composites. 307 * @param itemContainer The container of view items 308 * @param itemPath The path of the container to get 309 * @return The {@link ModelViewItemGroup}. Can never be <code>null</code> 310 * @throws UndefinedItemPathException If one of the parts of the given path is undefined 311 * @throws BadItemTypeException If one of the parts of the given path is defined, but is not of the correct type 312 */ 313 public static ModelViewItemGroup getModelViewItemGroup(ViewItemContainer itemContainer, String itemPath) throws UndefinedItemPathException, BadItemTypeException 314 { 315 return new ViewItemGetter(itemContainer).getViewItemContainer(itemPath); 316 } 317 318 private static class ViewItemGetter 319 { 320 private final ViewItemContainer _itemContainer; 321 private String _wholePath; 322 323 ViewItemGetter(ViewItemContainer view) 324 { 325 _itemContainer = view; 326 } 327 328 ModelViewItem getViewItem(String wholePath) throws UndefinedItemPathException, BadItemTypeException 329 { 330 _wholePath = wholePath; 331 BiFunction<ViewItemContainer, String, ModelViewItem> lastPartGetter = (viewItemContainer, lastPart) -> _getDirectViewItem(viewItemContainer, lastPart, ModelViewItem.class); 332 return _getModelViewItem(lastPartGetter); 333 } 334 335 ViewElement getModelViewItem(String wholePath) throws UndefinedItemPathException, BadItemTypeException 336 { 337 _wholePath = wholePath; 338 BiFunction<ViewItemContainer, String, ViewElement> lastPartGetter = (viewItemContainer, lastPart) -> _getDirectViewItem(viewItemContainer, lastPart, ViewElement.class); 339 return _getModelViewItem(lastPartGetter); 340 } 341 342 ModelViewItemGroup getViewItemContainer(String wholePath) throws UndefinedItemPathException, BadItemTypeException 343 { 344 _wholePath = wholePath; 345 BiFunction<ViewItemContainer, String, ModelViewItemGroup> lastPartGetter = (viewItemContainer, lastPart) -> _getDirectViewItemContainer(viewItemContainer, lastPart); 346 return _getModelViewItem(lastPartGetter); 347 } 348 349 private <T> T _getModelViewItem(BiFunction<ViewItemContainer, String, T> lastPartGetter) throws UndefinedItemPathException, BadItemTypeException 350 { 351 Deque<String> parts = new ArrayDeque<>(Arrays.asList(_wholePath.split(ModelItem.ITEM_PATH_SEPARATOR))); 352 String lastPart = parts.removeLast(); 353 ViewItemContainer currentViewItemContainer = _itemContainer; 354 while (!parts.isEmpty()) 355 { 356 String currentPart = parts.pop(); 357 currentViewItemContainer = _getDirectViewItemContainer(currentViewItemContainer, currentPart); 358 } 359 360 return lastPartGetter.apply(currentViewItemContainer, lastPart); 361 } 362 363 private ModelViewItemGroup _getDirectViewItemContainer(ViewItemContainer viewItemContainer, String itemName) throws UndefinedItemPathException, BadItemTypeException 364 { 365 return _getDirectViewItem(viewItemContainer, itemName, ModelViewItemGroup.class); 366 } 367 368 private <T> T _getDirectViewItem(ViewItemContainer viewItemContainer, String itemName, Class<T> resultClass) throws UndefinedItemPathException, BadItemTypeException 369 { 370 if (!viewItemContainer.hasModelViewItem(itemName)) 371 { 372 throw new UndefinedItemPathException("For path '" + _wholePath + "', the part '" + itemName + "' is not defined"); 373 } 374 else 375 { 376 ModelViewItem modelViewItem = viewItemContainer.getModelViewItem(itemName); 377 if (resultClass.isInstance(modelViewItem)) 378 { 379 return resultClass.cast(modelViewItem); 380 } 381 else 382 { 383 throw new BadItemTypeException("For path '" + _wholePath + "', the part '" + itemName + "' does not point to a '" + resultClass + "' (got a '" + modelViewItem.getClass().getName() + "')"); 384 } 385 } 386 } 387 } 388}