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.ArrayList; 019import java.util.Collection; 020import java.util.Collections; 021import java.util.LinkedHashMap; 022import java.util.List; 023import java.util.Map; 024import java.util.Objects; 025import java.util.Optional; 026 027import org.apache.cocoon.ProcessingException; 028import org.apache.cocoon.xml.AttributesImpl; 029import org.apache.commons.lang3.StringUtils; 030import org.xml.sax.ContentHandler; 031import org.xml.sax.SAXException; 032 033import org.ametys.core.util.XMLUtils; 034import org.ametys.runtime.i18n.I18nizableText; 035import org.ametys.runtime.model.exception.BadItemTypeException; 036import org.ametys.runtime.util.ModifiableLabelable; 037 038/** 039 * View of items 040 */ 041public class View implements ViewItemContainer, ModifiableLabelable 042{ 043 private String _name; 044 private I18nizableText _label; 045 private I18nizableText _description; 046 private String _iconGlyph; 047 private String _iconDecorator; 048 private String _smallIcon; 049 private String _mediumIcon; 050 private String _largeIcon; 051 private boolean _isInternal; 052 053 private List<ViewItem> _items = new ArrayList<>(); 054 055 /** 056 * Creates a {@link View} with the items of the given {@link Model} 057 * @param model the model 058 * @return the created {@link View} 059 * @throws IllegalArgumentException if the model is <code>null</code> 060 */ 061 public static View of(Model model) throws IllegalArgumentException 062 { 063 if (model == null) 064 { 065 throw new IllegalArgumentException("Unable to create the view from a null model"); 066 } 067 else 068 { 069 return ViewHelper.createViewItemAccessor(List.of(model)); 070 } 071 } 072 073 /** 074 * Creates a {@link View} with the items of the given {@link Model}s 075 * @param models the models 076 * @return the created {@link View} 077 * @throws IllegalArgumentException if the models collection is empty 078 */ 079 public static View of(Collection<? extends Model> models) throws IllegalArgumentException 080 { 081 return ViewHelper.createViewItemAccessor(models); 082 } 083 084 /** 085 * Creates a {@link View} with the given items 086 * @param model the model containing items definitions 087 * @param itemPaths the paths of the items to put in the view 088 * @return the created {@link View} 089 * @throws IllegalArgumentException if the model is <code>null</code> or if an item path is <code>null</code>, empty, or is not defined in the given models 090 * @throws BadItemTypeException if a segment in a path (but not the last) does not represent a group item 091 */ 092 public static View of(Model model, String... itemPaths) throws IllegalArgumentException, BadItemTypeException 093 { 094 if (model == null) 095 { 096 throw new IllegalArgumentException("Unable to create the view from a null model"); 097 } 098 else 099 { 100 return ViewHelper.createViewItemAccessor(List.of(model), itemPaths); 101 } 102 } 103 104 /** 105 * Creates a {@link View} with the given items 106 * @param models the models containing items definitions 107 * @param itemPaths the paths of the items to put in the view 108 * @return the created {@link View} 109 * @throws IllegalArgumentException if the models collection is empty or if an item path is <code>null</code>, empty, or is not defined in the given models 110 * @throws BadItemTypeException if a segment in a path (but not the last) does not represent a group item 111 */ 112 public static View of(Collection<? extends Model> models, String... itemPaths) throws IllegalArgumentException, BadItemTypeException 113 { 114 return ViewHelper.createViewItemAccessor(models, itemPaths); 115 } 116 117 /** 118 * Creates a {@link View} with the given items. If the items are in a group, the hierarchy will be kept and the corresponding containers will be created 119 * @param modelItems the items to put in the view 120 * @return the created {@link View} 121 */ 122 public static View of(ModelItem... modelItems) 123 { 124 View view = new View(); 125 126 for (ModelItem modelItem : modelItems) 127 { 128 ViewHelper.addViewItem(modelItem.getPath(), view, modelItem.getModel()); 129 } 130 131 return view; 132 } 133 134 /** 135 * Copy the current view in the given one. 136 * Its view items are not copied 137 * @param view the copy 138 */ 139 public void copyTo(View view) 140 { 141 view.setName(getName()); 142 view.setLabel(getLabel()); 143 view.setDescription(getDescription()); 144 view.setInternal(isInternal()); 145 146 view.setIconGlyph(getIconGlyph()); 147 view.setIconDecorator(getIconDecorator()); 148 view.setSmallIcon(getSmallIcon()); 149 view.setMediumIcon(getMediumIcon()); 150 view.setLargeIcon(getLargeIcon()); 151 } 152 153 public String getName() 154 { 155 return _name; 156 } 157 158 public void setName(String name) 159 { 160 _name = name; 161 } 162 163 public I18nizableText getLabel() 164 { 165 return _label; 166 } 167 168 public void setLabel(I18nizableText label) 169 { 170 _label = label; 171 } 172 173 public I18nizableText getDescription() 174 { 175 return _description; 176 } 177 178 public void setDescription(I18nizableText description) 179 { 180 _description = description; 181 } 182 183 /** 184 * Retrieves the CSS class to use for glyph icon 185 * @return the glyph name. 186 */ 187 public String getIconGlyph() 188 { 189 return _iconGlyph; 190 } 191 192 /** 193 * Set the CSS class to use for glyph icon 194 * @param iconGlyph the glyph name. 195 */ 196 public void setIconGlyph(String iconGlyph) 197 { 198 _iconGlyph = iconGlyph; 199 } 200 201 /** 202 * Retrieves the CSS class to use for decorator above the main icon 203 * @return the glyph name. 204 */ 205 public String getIconDecorator() 206 { 207 return _iconDecorator; 208 } 209 210 /** 211 * Set the CSS class to use for decorator above the main icon 212 * @param iconDecorator the glyph name. 213 */ 214 public void setIconDecorator(String iconDecorator) 215 { 216 _iconDecorator = iconDecorator; 217 } 218 219 /** 220 * Retrieves the URL of the small icon without the context path. 221 * @return the icon URL for the small image 16x16. 222 */ 223 public String getSmallIcon() 224 { 225 return _smallIcon; 226 } 227 228 /** 229 * Set the URL of the small icon. 230 * @param smallIcon the URL of the small icon, without the context path. 231 */ 232 public void setSmallIcon(String smallIcon) 233 { 234 _smallIcon = smallIcon; 235 } 236 237 /** 238 * Retrieves the URL of the small icon without the context path. 239 * @return the icon URL for the medium sized image 32x32. 240 */ 241 public String getMediumIcon() 242 { 243 return _mediumIcon; 244 } 245 246 /** 247 * Set the URL of the medium icon. 248 * @param mediumIcon the URL of the medium icon, without the context path. 249 */ 250 public void setMediumIcon(String mediumIcon) 251 { 252 _mediumIcon = mediumIcon; 253 } 254 255 /** 256 * Retrieves the URL of the small icon without the context path. 257 * @return the icon URL for the large image 48x48. 258 */ 259 public String getLargeIcon() 260 { 261 return _largeIcon; 262 } 263 264 /** 265 * Set the URL of the large icon. 266 * @param largeIcon the URL of the large icon, without the context path. 267 */ 268 public void setLargeIcon(String largeIcon) 269 { 270 _largeIcon = largeIcon; 271 } 272 273 /** 274 * Determines if the view is for internal use only 275 * @return <code>true</code> if the view is for internal use only, <code>false</code> otherwise 276 */ 277 public boolean isInternal() 278 { 279 return _isInternal; 280 } 281 282 /** 283 * Set the internal status 284 * @param isInternal <code>true</code> to make the view for internal use only, <code>false</code> otherwise 285 */ 286 public void setInternal(boolean isInternal) 287 { 288 _isInternal = isInternal; 289 } 290 291 public List<ViewItem> getViewItems() 292 { 293 return Collections.unmodifiableList(this._items); 294 } 295 296 public void addViewItem(ViewItem item) 297 { 298 _items.add(item); 299 item.setParent(this); 300 } 301 302 public void insertViewItem(ViewItem item, int index) 303 { 304 if (index >= 0 && index <= _items.size()) 305 { 306 _items.add(index, item); 307 } 308 else 309 { 310 throw new IllegalArgumentException("Unable to insert an item at index " + index + ". This group contains " + _items.size() + " items."); 311 } 312 } 313 314 public boolean removeViewItem(ViewItem item) 315 { 316 return _items.remove(item); 317 } 318 319 public void clear() 320 { 321 _items.clear(); 322 } 323 324 /** 325 * Converts the view in a JSON map 326 * @param context the context of the definitions included in the view 327 * @return The view as a JSON map 328 * @throws ProcessingException If an error occurs when converting the view 329 */ 330 public Map<String, Object> toJSON(DefinitionContext context) throws ProcessingException 331 { 332 Map<String, Object> result = new LinkedHashMap<>(); 333 334 result.put("name", getName()); 335 result.put("internal", isInternal()); 336 result.put("label", getLabel()); 337 result.put("description", getDescription()); 338 339 result.put("icon-glyph", getIconGlyph()); 340 result.put("icon-decorator", getIconDecorator()); 341 result.put("small-icon-path", getSmallIcon()); 342 result.put("medium-icon-path", getMediumIcon()); 343 result.put("large-icon-path", getLargeIcon()); 344 345 result.put("elements", ViewHelper.viewItemsToJSON(getViewItems(), context)); 346 347 return result; 348 } 349 350 /** 351 * Generates SAX events for the view 352 * @param contentHandler the {@link ContentHandler} that will receive the SAX events 353 * @param context the context of the definitions included in the view 354 * @throws SAXException if an error occurs during the SAX events generation 355 */ 356 @SuppressWarnings("static-access") 357 public void toSAX(ContentHandler contentHandler, DefinitionContext context) throws SAXException 358 { 359 AttributesImpl attributes = new AttributesImpl(); 360 Optional.ofNullable(getName()).ifPresent(name -> attributes.addCDATAAttribute("name", name)); 361 attributes.addCDATAAttribute("internal", String.valueOf(isInternal())); 362 363 XMLUtils.startElement(contentHandler, "view", attributes); 364 365 XMLUtils.createI18nElementIfNotNull(contentHandler, "label", getLabel()); 366 XMLUtils.createI18nElementIfNotNull(contentHandler, "description", getDescription()); 367 368 XMLUtils.startElement(contentHandler, "icons"); 369 XMLUtils.createElementIfNotNull(contentHandler, "icon-glyph", getIconGlyph()); 370 XMLUtils.createElementIfNotNull(contentHandler, "icon-decorator", getIconDecorator()); 371 XMLUtils.createElementIfNotNull(contentHandler, "small-icon-path", getSmallIcon()); 372 XMLUtils.createElementIfNotNull(contentHandler, "medium-icon-path", getMediumIcon()); 373 XMLUtils.createElementIfNotNull(contentHandler, "large-icon-path", getLargeIcon()); 374 XMLUtils.endElement(contentHandler, "icons"); 375 376 for (ViewItem viewItem : getViewItems()) 377 { 378 viewItem.toSAX(contentHandler, context); 379 } 380 381 XMLUtils.endElement(contentHandler, "view"); 382 } 383 384 /** 385 * Include the given view to the current one. 386 * Add the items of the view to include if they are not already present in the current view 387 * @param viewToInclude the view to include 388 */ 389 public void includeView(View viewToInclude) 390 { 391 View referenceView = new View(); 392 referenceView.addViewItems(getViewItems()); 393 ViewHelper.addViewAccessorItems(this, viewToInclude, referenceView, StringUtils.EMPTY); 394 } 395 396 @Override 397 public int hashCode() 398 { 399 return Objects.hash(_items, _name); 400 } 401 402 @Override 403 public boolean equals(Object obj) 404 { 405 if (this == obj) 406 { 407 return true; 408 } 409 if (obj == null) 410 { 411 return false; 412 } 413 if (getClass() != obj.getClass()) 414 { 415 return false; 416 } 417 View other = (View) obj; 418 return Objects.equals(_items, other._items) && Objects.equals(_name, other._name); 419 } 420 421 /** 422 * Indicates whether some other object is "equal to" this one. 423 * @param obj the reference object with which to compare. 424 * @param checkDetails <code>true</code> to check the view's details during comparison (label, description, icon, ...) 425 * @return <code>true</code> if this object is the same as the given obj, <code>false</code> otherwise. 426 */ 427 public boolean equals(Object obj, boolean checkDetails) 428 { 429 if (!equals(obj)) 430 { 431 return false; 432 } 433 else if (checkDetails) 434 { 435 View other = (View) obj; 436 437 for (int i = 0; i < _items.size(); i++) 438 { 439 ViewItem item = _items.get(i); 440 ViewItem otherItem = other._items.get(i); 441 442 if (!item.equals(otherItem, checkDetails)) 443 { 444 return false; 445 } 446 } 447 448 return Objects.equals(_description, other._description) && Objects.equals(_iconDecorator, other._iconDecorator) && Objects.equals(_iconGlyph, other._iconGlyph) 449 && _isInternal == other._isInternal && Objects.equals(_label, other._label); 450 } 451 else 452 { 453 return true; 454 } 455 } 456}