1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.myfaces.tobago.renderkit.util;
21
22 import org.apache.myfaces.tobago.component.Attributes;
23 import org.apache.myfaces.tobago.config.Configurable;
24 import org.apache.myfaces.tobago.context.ResourceManagerUtils;
25 import org.apache.myfaces.tobago.internal.component.AbstractUICommandBase;
26 import org.apache.myfaces.tobago.internal.component.AbstractUIData;
27 import org.apache.myfaces.tobago.internal.component.AbstractUITree;
28 import org.apache.myfaces.tobago.internal.util.StringUtils;
29 import org.apache.myfaces.tobago.layout.Measure;
30 import org.apache.myfaces.tobago.model.ExpandedState;
31 import org.apache.myfaces.tobago.model.MarkedState;
32 import org.apache.myfaces.tobago.model.TreePath;
33 import org.apache.myfaces.tobago.renderkit.RendererBase;
34 import org.apache.myfaces.tobago.util.ComponentUtils;
35 import org.apache.myfaces.tobago.util.DebugUtils;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
38
39 import javax.faces.application.Application;
40 import javax.faces.application.ViewHandler;
41 import javax.faces.component.EditableValueHolder;
42 import javax.faces.component.UIComponent;
43 import javax.faces.component.UIParameter;
44 import javax.faces.component.UISelectItem;
45 import javax.faces.component.UISelectItems;
46 import javax.faces.component.ValueHolder;
47 import javax.faces.context.ExternalContext;
48 import javax.faces.context.FacesContext;
49 import javax.faces.convert.Converter;
50 import javax.faces.convert.ConverterException;
51 import javax.faces.model.SelectItem;
52 import java.io.IOException;
53 import java.net.URLDecoder;
54 import java.util.ArrayList;
55 import java.util.Arrays;
56 import java.util.Collection;
57 import java.util.Iterator;
58 import java.util.List;
59 import java.util.Map;
60
61 public class RenderUtils {
62
63 private static final Logger LOG = LoggerFactory.getLogger(RenderUtils.class);
64
65 public static final String COMPONENT_IN_REQUEST = "org.apache.myfaces.tobago.component";
66
67 private RenderUtils() {
68
69 }
70
71 public static boolean contains(Object[] list, Object value) {
72 if (list == null) {
73 return false;
74 }
75 for (Object aList : list) {
76 if (aList != null && aList.equals(value)) {
77 return true;
78 }
79 }
80 return false;
81 }
82
83 public static void encodeChildren(FacesContext facesContext, UIComponent panel) throws IOException {
84 for (UIComponent child : panel.getChildren()) {
85 encode(facesContext, child);
86 }
87 }
88
89 public static void encode(FacesContext facesContext, UIComponent component) throws IOException {
90 encode(facesContext, component, null);
91 }
92
93 public static void encode(
94 FacesContext facesContext, UIComponent component, List<? extends Class<? extends UIComponent>> only)
95 throws IOException {
96
97 if (only != null && !matchFilter(component, only)) {
98 return;
99 }
100
101 if (component.isRendered()) {
102 if (LOG.isDebugEnabled()) {
103 LOG.debug("rendering " + component.getRendererType() + " " + component);
104 }
105 component.encodeBegin(facesContext);
106 if (component.getRendersChildren()) {
107 component.encodeChildren(facesContext);
108 } else {
109 for (UIComponent child : component.getChildren()) {
110 encode(facesContext, child, only);
111 }
112 }
113 component.encodeEnd(facesContext);
114 }
115 }
116
117 private static boolean matchFilter(UIComponent component, List<? extends Class<? extends UIComponent>> only) {
118 for (Class clazz : only) {
119 if (clazz.isAssignableFrom(component.getClass())) {
120 return true;
121 }
122 }
123 return false;
124 }
125
126 public static void prepareRendererAll(FacesContext facesContext, UIComponent component) throws IOException {
127 if (!component.isRendered()) {
128 return;
129 }
130 RendererBase renderer = ComponentUtils.getRenderer(facesContext, component);
131 boolean prepareRendersChildren = false;
132 if (renderer != null) {
133 renderer.prepareRender(facesContext, component);
134 prepareRendersChildren = renderer.getPrepareRendersChildren();
135 }
136 if (prepareRendersChildren) {
137 renderer.prepareRendersChildren(facesContext, component);
138 } else {
139 Iterator it = component.getFacetsAndChildren();
140 while (it.hasNext()) {
141 UIComponent child = (UIComponent) it.next();
142 prepareRendererAll(facesContext, child);
143 }
144 }
145 }
146
147 public static String getFormattedValue(
148 FacesContext facesContext, UIComponent component) {
149 Object value = null;
150 if (component instanceof ValueHolder) {
151 value = ((ValueHolder) component).getLocalValue();
152 if (value == null) {
153 value = ((ValueHolder) component).getValue();
154 }
155 }
156 return getFormattedValue(facesContext, component, value);
157 }
158
159
160 public static String getFormattedValue(
161 FacesContext context, UIComponent component, Object currentValue)
162 throws ConverterException {
163
164 if (currentValue == null) {
165 return "";
166 }
167
168 if (!(component instanceof ValueHolder)) {
169 return currentValue.toString();
170 }
171
172 Converter converter = ((ValueHolder) component).getConverter();
173
174 if (converter == null) {
175 if (currentValue instanceof String) {
176 return (String) currentValue;
177 }
178 Class converterType = currentValue.getClass();
179 converter = context.getApplication().createConverter(converterType);
180 }
181
182 if (converter == null) {
183 return currentValue.toString();
184 } else {
185 return converter.getAsString(context, component, currentValue);
186 }
187 }
188
189 public static Measure calculateStringWidth(FacesContext facesContext, UIComponent component, String text) {
190 return calculateStringWidth(facesContext, (Configurable) component, text, "tobago.font.widths");
191 }
192
193 public static Measure calculateStringWidth2(FacesContext facesContext, UIComponent component, String text) {
194 return calculateStringWidth(facesContext, (Configurable) component, text, "tobago.font2.widths");
195 }
196
197 private static Measure calculateStringWidth(
198 FacesContext facesContext, Configurable component, String text, String type) {
199 if (text == null) {
200 return Measure.ZERO;
201 }
202 int width = 0;
203 int defaultCharWidth = 10;
204 try {
205 defaultCharWidth = ResourceManagerUtils.getThemeMeasure(facesContext, component, "fontWidth").getPixel();
206 } catch (NullPointerException e) {
207 LOG.warn("no value for 'fontWidth' for type '" + component.getRendererType() + "' found in theme-config");
208 }
209
210 String fontWidths = ResourceManagerUtils.getProperty(facesContext, "tobago", type);
211
212 for (char c : text.toCharArray()) {
213 if (c >= 32 && c < 128) {
214 int begin = (c - 32) * 2;
215 width += Integer.parseInt(fontWidths.substring(begin, begin + 2), 16);
216 } else {
217 width += defaultCharWidth;
218 }
219 }
220
221 return Measure.valueOf(width);
222 }
223
224 public static List<SelectItem> getItemsToRender(javax.faces.component.UISelectOne component) {
225 return getItems(component);
226 }
227
228 public static List<SelectItem> getItemsToRender(javax.faces.component.UISelectMany component) {
229 return getItems(component);
230 }
231
232 public static List<SelectItem> getItems(javax.faces.component.UIInput component) {
233
234 List<SelectItem> selectItems = getSelectItems(component);
235
236 String renderRange = (String) component.getAttributes().get(Attributes.RENDER_RANGE_EXTERN);
237 if (renderRange == null) {
238 renderRange = (String) component.getAttributes().get(Attributes.RENDER_RANGE);
239 }
240 if (renderRange == null) {
241 return selectItems;
242 }
243
244 int[] indices = StringUtils.getIndices(renderRange);
245 List<SelectItem> items = new ArrayList<SelectItem>(indices.length);
246
247 if (selectItems.size() != 0) {
248 for (int indice : indices) {
249 items.add(selectItems.get(indice));
250 }
251 } else {
252 LOG.warn("No items found! rendering dummies instead!");
253 for (int i = 0; i < indices.length; i++) {
254 items.add(new SelectItem(Integer.toString(i), "Item " + i, ""));
255 }
256 }
257 return items;
258 }
259
260 public static String currentValue(UIComponent component) {
261 String currentValue = null;
262 if (component instanceof ValueHolder) {
263 Object value;
264 if (component instanceof EditableValueHolder) {
265 value = ((EditableValueHolder) component).getSubmittedValue();
266 if (value != null) {
267 return (String) value;
268 }
269 }
270
271 value = ((ValueHolder) component).getValue();
272 if (value != null) {
273 Converter converter = ((ValueHolder) component).getConverter();
274 if (converter == null) {
275 FacesContext context = FacesContext.getCurrentInstance();
276 converter = context.getApplication().createConverter(value.getClass());
277 }
278 if (converter != null) {
279 currentValue =
280 converter.getAsString(FacesContext.getCurrentInstance(),
281 component, value);
282 } else {
283 currentValue = value.toString();
284 }
285 }
286 }
287 return currentValue;
288 }
289
290 public static List<SelectItem> getSelectItems(UIComponent component) {
291
292 ArrayList<SelectItem> list = new ArrayList<SelectItem>();
293
294 for (UIComponent child : component.getChildren()) {
295 if (LOG.isDebugEnabled()) {
296 LOG.debug("kid " + child);
297 LOG.debug("kid " + child.getClass().getName());
298 }
299 if (child instanceof UISelectItem) {
300 Object value = ((UISelectItem) child).getValue();
301 if (value == null) {
302 UISelectItem item = (UISelectItem) child;
303 if (child instanceof org.apache.myfaces.tobago.component.UISelectItem) {
304 list.add(getSelectItem(
305 (org.apache.myfaces.tobago.component.UISelectItem) child));
306 } else {
307 list.add(new SelectItem(item.getItemValue() == null ? "" : item.getItemValue(),
308 item.getItemLabel() != null ? item.getItemLabel() : item.getItemValue().toString(),
309 item.getItemDescription()));
310 }
311 } else if (value instanceof SelectItem) {
312 list.add((SelectItem) value);
313 } else {
314 final String message
315 = "TYPE ERROR: value NOT instanceof SelectItem. type="
316 + value.getClass().getName() + " value=" + value;
317 LOG.error(message);
318 DebugUtils.addDevelopmentMessage(FacesContext.getCurrentInstance(), message);
319 }
320 } else if (child instanceof UISelectItems) {
321 Object value = ((UISelectItems) child).getValue();
322 if (LOG.isDebugEnabled()) {
323 LOG.debug("value " + value);
324 if (value != null) {
325 LOG.debug("value " + value.getClass().getName());
326 }
327 }
328 if (value == null) {
329 if (LOG.isDebugEnabled()) {
330 LOG.debug("value is null");
331 }
332 } else if (value instanceof SelectItem) {
333 list.add((SelectItem) value);
334 } else if (value instanceof SelectItem[]) {
335 SelectItem[] items = (SelectItem[]) value;
336 list.addAll(Arrays.asList(items));
337 } else if (value instanceof Collection) {
338 for (Object o : ((Collection) value)) {
339 list.add((SelectItem) o);
340 }
341 } else if (value instanceof Map) {
342 for (Object key : ((Map) value).keySet()) {
343 if (key != null) {
344 Object val = ((Map) value).get(key);
345 if (val != null) {
346 list.add(new SelectItem(val.toString(), key.toString(), null));
347 }
348 }
349 }
350 } else {
351 final String message
352 = "TYPE ERROR: value NOT instanceof SelectItem, SelectItem[], Collection, Map. type="
353 + value.getClass().getName() + " value=" + value;
354 LOG.error(message);
355 DebugUtils.addDevelopmentMessage(FacesContext.getCurrentInstance(), message);
356 }
357 }
358 }
359
360 return list;
361 }
362
363 private static SelectItem getSelectItem(org.apache.myfaces.tobago.component.UISelectItem component) {
364 return
365 new org.apache.myfaces.tobago.model.SelectItem(component.getItemValue() == null ? "" : component.getItemValue(),
366 component.getItemLabel(), component.getItemDescription(),
367 component.isItemDisabled(), component.getItemImage(), component.getMarkup());
368 }
369
370
371 public static void decodedStateOfTreeData(FacesContext facesContext, AbstractUIData data) {
372
373 if (!data.isTreeModel()) {
374 return;
375 }
376
377 final boolean isTree = data instanceof AbstractUITree;
378
379 final Integer markedIndex = isTree ? decodeMarkedIndex(facesContext, data) : null;
380
381
382 final List<Integer> expandedIndices = decodeExpandedIndices(facesContext, data);
383
384 final int last = data.isRowsUnlimited() ? Integer.MAX_VALUE : data.getFirst() + data.getRows();
385 for (int rowIndex = data.getFirst(); rowIndex < last; rowIndex++) {
386 data.setRowIndex(rowIndex);
387 if (!data.isRowAvailable()) {
388 break;
389 }
390
391 final TreePath path = data.getPath();
392
393
394 if (isTree) {
395 final MarkedState markedState = ((AbstractUITree) data).getMarkedState();
396 final boolean oldMarked = markedState.isMarked(path);
397 final boolean newMarked = ((Integer) rowIndex).equals(markedIndex);
398 if (newMarked != oldMarked) {
399 if (newMarked) {
400 markedState.setMarked(path);
401 } else {
402 markedState.setMarked((TreePath) null);
403 }
404 }
405 }
406
407
408 if (expandedIndices != null) {
409 final ExpandedState expandedState = data.getExpandedState();
410 final boolean oldExpanded = expandedState.isExpanded(path);
411 final boolean newExpanded = expandedIndices.contains(rowIndex);
412 if (newExpanded != oldExpanded) {
413 if (newExpanded) {
414 expandedState.expand(path);
415 } else {
416 expandedState.collapse(path);
417 }
418 }
419 }
420
421 }
422 data.setRowIndex(-1);
423 }
424
425 private static Integer decodeMarkedIndex(FacesContext facesContext, AbstractUIData data) {
426 String markedString = null;
427 try {
428 markedString = (String) facesContext.getExternalContext().getRequestParameterMap()
429 .get(data.getClientId(facesContext) + ComponentUtils.SUB_SEPARATOR + AbstractUIData.SUFFIX_MARKED);
430 if (org.apache.commons.lang.StringUtils.isBlank(markedString)) {
431 return null;
432 }
433 return Integer.parseInt(markedString);
434 } catch (Exception e) {
435
436 LOG.warn("Can't parse marked: '" + markedString + "'", e);
437 return null;
438 }
439 }
440
441 private static List<Integer> decodeExpandedIndices(FacesContext facesContext, AbstractUIData data) {
442 String expandedString = null;
443 try {
444 expandedString = (String) facesContext.getExternalContext().getRequestParameterMap()
445 .get(data.getClientId(facesContext) + ComponentUtils.SUB_SEPARATOR + AbstractUIData.SUFFIX_EXPANDED);
446 if (expandedString != null) {
447 return StringUtils.parseIntegerList(expandedString);
448 }
449 } catch (Exception e) {
450
451 LOG.warn("Can't parse expanded: '" + expandedString + "'", e);
452 }
453 return null;
454 }
455
456 public static String generateUrl(FacesContext facesContext, AbstractUICommandBase component) {
457
458 final Application application = facesContext.getApplication();
459 final ViewHandler viewHandler = application.getViewHandler();
460 final ExternalContext externalContext = facesContext.getExternalContext();
461
462 String url = null;
463
464 if (component.getResource() != null) {
465 boolean jsfResource = component.isJsfResource();
466 url = ResourceManagerUtils.getPageWithoutContextPath(facesContext, component.getResource());
467 if (url != null) {
468 if (jsfResource) {
469 url = viewHandler.getActionURL(facesContext, url);
470 url = externalContext.encodeActionURL(url);
471 } else {
472 url = viewHandler.getResourceURL(facesContext, url);
473 url = externalContext.encodeResourceURL(url);
474 }
475 } else {
476 url = "";
477 }
478 } else if (component.getLink() != null) {
479
480 String link = component.getLink();
481 if (link.startsWith("/")) {
482 url = viewHandler.getActionURL(facesContext, link);
483 url = externalContext.encodeActionURL(url);
484 } else if (link.contains(":")) {
485 url = link;
486 } else {
487 url = externalContext.encodeResourceURL(link);
488 }
489
490 StringBuilder builder = new StringBuilder(url);
491 boolean firstParameter = !url.contains("?");
492 for (UIComponent child : component.getChildren()) {
493 if (child instanceof UIParameter) {
494 UIParameter parameter = (UIParameter) child;
495 if (firstParameter) {
496 builder.append("?");
497 firstParameter = false;
498 } else {
499 builder.append("&");
500 }
501 builder.append(parameter.getName());
502 builder.append("=");
503 Object value = parameter.getValue();
504
505 builder.append(value != null ? URLDecoder.decode(value.toString()) : null);
506 }
507 }
508 url = builder.toString();
509 }
510
511 return url;
512 }
513
514 }