1 16 package javax.faces.component; 17 18 import java.io.IOException ; 19 import java.io.Serializable ; 20 import java.sql.ResultSet ; 21 import java.util.ArrayList ; 22 import java.util.Collection ; 23 import java.util.HashMap ; 24 import java.util.Iterator ; 25 import java.util.List ; 26 import java.util.Map ; 27 28 import javax.faces.context.FacesContext; 29 import javax.faces.el.ValueBinding; 30 import javax.faces.event.AbortProcessingException; 31 import javax.faces.event.FacesEvent; 32 import javax.faces.event.FacesListener; 33 import javax.faces.event.PhaseId; 34 import javax.faces.model.ArrayDataModel; 35 import javax.faces.model.DataModel; 36 import javax.faces.model.ListDataModel; 37 import javax.faces.model.ResultDataModel; 38 import javax.faces.model.ResultSetDataModel; 39 import javax.faces.model.ScalarDataModel; 40 import javax.servlet.jsp.jstl.sql.Result; 41 42 81 public class UIData extends UIComponentBase implements NamingContainer 82 { 83 private static final int STATE_SIZE = 6; 84 private static final int SUPER_STATE_INDEX = 0; 85 private static final int FIRST_INDEX = 1; 86 private static final int ROWS_INDEX = 2; 87 private static final int VALUE_INDEX = 3; 88 private static final int VAR_INDEX = 4; 89 private static final int ROW_STATE_INDEX = 5; 90 91 private static final String FOOTER_FACET_NAME = "footer"; 92 private static final String HEADER_FACET_NAME = "header"; 93 private static final Class OBJECT_ARRAY_CLASS = (new Object [0]).getClass(); 94 private static final int PROCESS_DECODES = 1; 95 private static final int PROCESS_VALIDATORS = 2; 96 private static final int PROCESS_UPDATES = 3; 97 98 100 private int _rowIndex = -1; 101 private String _var = null; 102 105 private UIDataRowState _rowState = new UIDataRowState(); 106 107 transient private boolean _saveDescendantStates = false; 109 110 transient private boolean _firstTimeRendered = true; 112 113 private Boolean _isEmbeddedUIData = null; 114 private UIData _embeddingUIData = null; 115 private DataModel _dataModel = null; 116 private HashMap _dataModelMap = null; 117 118 public void setFooter(UIComponent footer) 119 { 120 getFacets().put(FOOTER_FACET_NAME, footer); 121 } 122 123 public UIComponent getFooter() 124 { 125 return (UIComponent) getFacets().get(FOOTER_FACET_NAME); 126 } 127 128 public void setHeader(UIComponent header) 129 { 130 getFacets().put(HEADER_FACET_NAME, header); 131 } 132 133 public UIComponent getHeader() 134 { 135 return (UIComponent) getFacets().get(HEADER_FACET_NAME); 136 } 137 138 public boolean isRowAvailable() 139 { 140 return getDataModel().isRowAvailable(); 141 } 142 143 public int getRowCount() 144 { 145 return getDataModel().getRowCount(); 146 } 147 148 public Object getRowData() 149 { 150 return getDataModel().getRowData(); 151 } 152 153 public int getRowIndex() 154 { 155 return _rowIndex; 156 } 157 158 public void setRowIndex(int rowIndex) 159 { 160 saveDescendantComponentStates(getFacesContext(), this); 161 162 _rowIndex = rowIndex; 163 164 DataModel dataModel = getDataModel(); 165 dataModel.setRowIndex(rowIndex); 166 167 String var = getVar(); 168 if (rowIndex == -1) 169 { 170 if (var != null) 171 { 172 getFacesContext().getExternalContext().getRequestMap().remove(var); 173 } 174 } 175 else 176 { 177 if (var != null) 178 { 179 if (isRowAvailable()) 180 { 181 Object rowData = dataModel.getRowData(); 182 getFacesContext().getExternalContext().getRequestMap().put(var, rowData); 183 } 184 else 185 { 186 getFacesContext().getExternalContext().getRequestMap().remove(var); 187 } 188 } 189 } 190 191 restoreDescendantComponentStates(getFacesContext(), this, true); 192 } 193 194 207 private void saveDescendantComponentStates(FacesContext context, UIComponent component) 208 { 209 for (Iterator i = component.getFacetsAndChildren(); i.hasNext();) 210 { 211 213 UIComponent child = (UIComponent) i.next(); 214 if (child instanceof UIData) 215 { 216 UIData childUIData = (UIData) child; 217 _rowState._clientIdsToChildUIDataStates.put( 218 childUIData.getClientId(context), 219 childUIData._rowState); 220 continue; 221 } 222 223 if (child instanceof EditableValueHolder) 224 { 225 EditableValueHolder childEVH = (EditableValueHolder) child; 226 _rowState._clientIdsToChildEVHStates.put( 227 child.getClientId(context), 228 new EditableValueHolderState(childEVH)); 229 } 230 231 saveDescendantComponentStates(context, child); 232 } 233 } 234 235 275 294 307 322 339 private static String getInitialClientId(FacesContext context, UIComponent component) 340 { 341 347 int oldRowIndex = 0; 348 UIData parentUIData = null; 349 350 for (UIComponent parent = component.getParent(); parent != null; parent = parent.getParent()) 351 { 352 if (parent instanceof UIData) 353 { 354 parentUIData = (UIData) parent; 355 oldRowIndex = parentUIData._rowIndex; 356 parentUIData._rowIndex = -1; 357 break; 358 } 359 } 360 361 if (parentUIData == null) 362 { 363 throw new IllegalStateException ( 364 "Couldn't find a parent UIData for " + component.getClientId(context)); 365 } 366 367 component.setId(component.getId()); 369 String clientId = component.getClientId(context); 370 371 parentUIData._rowIndex = oldRowIndex; 372 373 component.setId(component.getId()); 374 375 return clientId; 376 } 377 378 private void restoreDescendantComponentStates( 379 FacesContext context, 380 UIComponent component, 381 boolean saveState) 382 { 383 for (Iterator i = component.getFacetsAndChildren(); i.hasNext();) 384 { 385 UIComponent child = (UIComponent) i.next(); 386 child.setId(child.getId()); 388 390 if (saveState) 391 { 392 if (child instanceof UIData) 394 { 395 UIData childUIData = (UIData) child; 396 Object state = 397 _rowState._clientIdsToChildUIDataStates.get( 398 childUIData.getClientId(context)); 399 if (state == null) 400 { 401 UIDataRowState initialState = 402 (UIDataRowState) _rowState._clientIdsToChildUIDataStates.get(getInitialClientId(context, child)); 403 404 if (initialState == null) 405 { 406 throw new IllegalStateException ( 407 "No initial state defined for clientId: " + child.getClientId(context)); 408 } 409 410 state = new UIDataRowState(initialState); 411 } 412 413 414 415 childUIData._rowState = (UIDataRowState) state; 416 417 restoreDescendantComponentStates(context, component, false); 418 continue; 419 } 420 421 if (!_firstTimeRendered && child instanceof EditableValueHolder) 422 { 423 EditableValueHolder childEVH = (EditableValueHolder) child; 424 Object state = 425 _rowState._clientIdsToChildEVHStates.get(child.getClientId(context)); 426 if (state == null) 427 { 428 state = 429 _rowState._clientIdsToChildEVHStates.get( 430 getInitialClientId(context, child)); 431 } 432 ((EditableValueHolderState) state).restore(childEVH); 433 } 434 } 435 436 restoreDescendantComponentStates(context, child, saveState); 437 } 438 } 439 440 487 525 public void setRows(int rows) 526 { 527 _rows = new Integer (rows); 528 if (rows < 0) 529 throw new IllegalArgumentException ("rows: " + rows); 530 } 531 532 public void setVar(String var) 533 { 534 _var = var; 535 } 536 537 public String getVar() 538 { 539 return _var; 540 } 541 542 public void setValueBinding(String name, ValueBinding binding) 543 { 544 if (name == null) 545 { 546 throw new NullPointerException ("name"); 547 } 548 else if (name.equals("value")) 549 { 550 _dataModel = null; 551 } 552 else if (name.equals("var") || name.equals("rowIndex")) 553 { 554 throw new IllegalArgumentException ("name " + name); 555 } 556 super.setValueBinding(name, binding); 557 } 558 559 public String getClientId(FacesContext context) 560 { 561 String clientId = super.getClientId(context); 562 int rowIndex = getRowIndex(); 563 if (rowIndex == -1) 564 { 565 return clientId; 566 } 567 else 568 { 569 return clientId + "_" + rowIndex; 570 } 571 } 572 573 public void queueEvent(FacesEvent event) 574 { 575 super.queueEvent(new FacesEventWrapper(event, getRowIndex(), this)); 576 } 577 578 public void broadcast(FacesEvent event) throws AbortProcessingException 579 { 580 if (event instanceof FacesEventWrapper) 581 { 582 FacesEvent originalEvent = ((FacesEventWrapper) event).getWrappedFacesEvent(); 583 int eventRowIndex = ((FacesEventWrapper) event).getRowIndex(); 584 int currentRowIndex = getRowIndex(); 585 setRowIndex(eventRowIndex); 586 originalEvent.getComponent().broadcast(originalEvent); 587 setRowIndex(currentRowIndex); 588 } 589 else 590 { 591 super.broadcast(event); 592 } 593 } 594 595 public void encodeBegin(FacesContext context) throws IOException 596 { 597 if (_firstTimeRendered || isAllChildrenAndFacetsValid()) 598 { 599 _saveDescendantStates = false; _dataModel = null; 602 if (_dataModelMap != null) 603 _dataModelMap.clear(); 604 } 605 else 606 { 607 _saveDescendantStates = true; 608 } 610 super.encodeBegin(context); 611 } 612 613 public void encodeEnd(FacesContext context) throws IOException 614 { 615 setRowIndex(-1); 616 617 super.encodeEnd(context); 618 } 619 620 private boolean isAllChildrenAndFacetsValid() 621 { 622 int first = getFirst(); 623 int rows = getRows(); 624 int last; 625 if (rows == 0) 626 { 627 last = getRowCount(); 628 } 629 else 630 { 631 last = first + rows; 632 } 633 int setRowIndex = getRowIndex(); 634 try 635 { 636 for (int rowIndex = first; rowIndex < last; rowIndex++) 637 { 638 setRowIndex(rowIndex); 639 if (isRowAvailable()) 640 { 641 if (!isAllEditableValueHoldersValidRecursive(getFacetsAndChildren())) 642 { 643 return false; 644 } 645 } 646 } 647 } 648 finally 649 { 650 setRowIndex(setRowIndex); 651 } 652 return true; 653 } 654 655 private boolean isAllEditableValueHoldersValidRecursive(Iterator facetsAndChildrenIterator) 656 { 657 while (facetsAndChildrenIterator.hasNext()) 658 { 659 UIComponent c = (UIComponent) facetsAndChildrenIterator.next(); 660 if (c instanceof EditableValueHolder && !((EditableValueHolder) c).isValid()) 661 { 662 return false; 663 } 664 if (!isAllEditableValueHoldersValidRecursive(c.getFacetsAndChildren())) 665 { 666 return false; 667 } 668 } 669 return true; 670 } 671 672 public void processDecodes(FacesContext context) 673 { 674 if (context == null) 675 throw new NullPointerException ("context"); 676 if (!isRendered()) 677 return; 678 setRowIndex(-1); 679 processFacets(context, PROCESS_DECODES); 680 processColumnFacets(context, PROCESS_DECODES); 681 processColumnChildren(context, PROCESS_DECODES); 682 setRowIndex(-1); 683 try 684 { 685 decode(context); 686 } 687 catch (RuntimeException e) 688 { 689 context.renderResponse(); 690 throw e; 691 } 692 } 693 694 public void processValidators(FacesContext context) 695 { 696 if (context == null) 697 throw new NullPointerException ("context"); 698 if (!isRendered()) 699 return; 700 setRowIndex(-1); 701 processFacets(context, PROCESS_VALIDATORS); 702 processColumnFacets(context, PROCESS_VALIDATORS); 703 processColumnChildren(context, PROCESS_VALIDATORS); 704 setRowIndex(-1); 705 } 706 707 public void processUpdates(FacesContext context) 708 { 709 if (context == null) 710 throw new NullPointerException ("context"); 711 if (!isRendered()) 712 return; 713 setRowIndex(-1); 714 processFacets(context, PROCESS_UPDATES); 715 processColumnFacets(context, PROCESS_UPDATES); 716 processColumnChildren(context, PROCESS_UPDATES); 717 setRowIndex(-1); 718 } 719 720 private void processFacets(FacesContext context, int processAction) 721 { 722 for (Iterator it = getFacets().values().iterator(); it.hasNext();) 723 { 724 UIComponent facet = (UIComponent) it.next(); 725 process(context, facet, processAction); 726 } 727 } 728 729 private void processColumnFacets(FacesContext context, int processAction) 730 { 731 for (Iterator childIter = getChildren().iterator(); childIter.hasNext();) 732 { 733 UIComponent child = (UIComponent) childIter.next(); 734 if (child instanceof UIColumn) 735 { 736 if (!child.isRendered()) 737 { 738 continue; 740 } 741 for (Iterator facetsIter = child.getFacets().values().iterator(); 742 facetsIter.hasNext(); 743 ) 744 { 745 UIComponent facet = (UIComponent) facetsIter.next(); 746 process(context, facet, processAction); 747 } 748 } 749 } 750 } 751 752 private void processColumnChildren(FacesContext context, int processAction) 753 { 754 int first = getFirst(); 755 int rows = getRows(); 756 int last; 757 if (rows == 0) 758 { 759 last = getRowCount(); 760 } 761 else 762 { 763 last = first + rows; 764 } 765 for (int rowIndex = first; rowIndex < last; rowIndex++) 766 { 767 setRowIndex(rowIndex); 768 if (isRowAvailable()) 769 { 770 for (Iterator it = getChildren().iterator(); it.hasNext();) 771 { 772 UIComponent child = (UIComponent) it.next(); 773 if (child instanceof UIColumn) 774 { 775 if (!child.isRendered()) 776 { 777 continue; 779 } 780 for (Iterator columnChildIter = child.getChildren().iterator(); 781 columnChildIter.hasNext(); 782 ) 783 { 784 UIComponent columnChild = (UIComponent) columnChildIter.next(); 785 process(context, columnChild, processAction); 786 } 787 } 788 } 789 } 790 } 791 } 792 793 private void process(FacesContext context, UIComponent component, int processAction) 794 { 795 switch (processAction) 796 { 797 case PROCESS_DECODES : 798 component.processDecodes(context); 799 break; 800 case PROCESS_VALIDATORS : 801 component.processValidators(context); 802 break; 803 case PROCESS_UPDATES : 804 component.processUpdates(context); 805 break; 806 } 807 } 808 809 private DataModel getDataModel() 810 { 811 UIData embeddingUIData = getEmbeddingUIData(); 812 if (embeddingUIData != null) 813 { 814 if (_dataModelMap == null) 820 { 821 _dataModelMap = new HashMap (); 822 } 823 String embeddingClientId = 824 embeddingUIData.getClientId(FacesContext.getCurrentInstance()); 825 DataModel dataModel = (DataModel) _dataModelMap.get(embeddingClientId); 826 if (dataModel == null) 827 { 828 dataModel = createDataModel(); 829 _dataModelMap.put(embeddingClientId, dataModel); 830 } 831 return dataModel; 832 } 833 else 834 { 835 if (_dataModel == null) 838 { 839 _dataModel = createDataModel(); 840 } 841 return _dataModel; 842 } 843 } 844 845 848 private DataModel createDataModel() 849 { 850 Object value = getValue(); 851 if (value == null) 852 { 853 return EMPTY_DATA_MODEL; 854 } 855 else if (value instanceof DataModel) 856 { 857 return (DataModel) value; 858 } 859 else if (value instanceof List ) 860 { 861 return new ListDataModel((List ) value); 862 } 863 else if (value instanceof Collection ) 864 { 865 return new ListDataModel(new ArrayList ((Collection ) value)); 866 } 867 else if (OBJECT_ARRAY_CLASS.isAssignableFrom(value.getClass())) 868 { 869 return new ArrayDataModel((Object []) value); 870 } 871 else if (value instanceof ResultSet ) 872 { 873 return new ResultSetDataModel((ResultSet ) value); 874 } 875 else if (value instanceof Result) 876 { 877 return new ResultDataModel((Result) value); 878 } 879 else 880 { 881 return new ScalarDataModel(value); 882 } 883 } 884 885 889 private UIData getEmbeddingUIData() 890 { 891 if (_isEmbeddedUIData == null) 892 { 893 UIComponent findParentUIData = getParent(); 894 while (findParentUIData != null && !(findParentUIData instanceof UIData)) 895 { 896 findParentUIData = findParentUIData.getParent(); 897 } 898 if (findParentUIData != null) 899 { 900 _embeddingUIData = (UIData) findParentUIData; 901 _isEmbeddedUIData = Boolean.TRUE; 902 } 903 else 904 { 905 _isEmbeddedUIData = Boolean.FALSE; 906 } 907 } 908 909 if (_isEmbeddedUIData.booleanValue()) 910 { 911 return _embeddingUIData; 912 } 913 else 914 { 915 return null; 916 } 917 } 918 919 private static class UIDataRowIndexState 920 { 921 private UIData _uiData; 922 private int _rowIndex; 923 924 public UIDataRowIndexState(UIData uiData) 925 { 926 _uiData = uiData; 927 _rowIndex = _uiData._rowIndex; 928 } 929 930 public void restore() 931 { 932 _uiData._rowIndex = _rowIndex; 933 } 934 } 935 936 private static class FacesEventWrapper extends FacesEvent 937 { 938 private FacesEvent _wrappedFacesEvent; 939 private int _rowIndex; 940 941 public FacesEventWrapper(FacesEvent facesEvent, int rowIndex, UIData redirectComponent) 942 { 943 super(redirectComponent); 944 _wrappedFacesEvent = facesEvent; 945 _rowIndex = rowIndex; 946 } 947 948 public PhaseId getPhaseId() 949 { 950 return _wrappedFacesEvent.getPhaseId(); 951 } 952 953 public void setPhaseId(PhaseId phaseId) 954 { 955 _wrappedFacesEvent.setPhaseId(phaseId); 956 } 957 958 public void queue() 959 { 960 _wrappedFacesEvent.queue(); 961 } 962 963 public String toString() 964 { 965 return _wrappedFacesEvent.toString(); 966 } 967 968 public boolean isAppropriateListener(FacesListener faceslistener) 969 { 970 return _wrappedFacesEvent.isAppropriateListener(faceslistener); 971 } 972 973 public void processListener(FacesListener faceslistener) 974 { 975 _wrappedFacesEvent.processListener(faceslistener); 976 } 977 978 public FacesEvent getWrappedFacesEvent() 979 { 980 return _wrappedFacesEvent; 981 } 982 983 public int getRowIndex() 984 { 985 return _rowIndex; 986 } 987 } 988 989 private static final DataModel EMPTY_DATA_MODEL = new DataModel() 990 { 991 public boolean isRowAvailable() 992 { 993 return false; 994 } 995 996 public int getRowCount() 997 { 998 return 0; 999 } 1000 1001 public Object getRowData() 1002 { 1003 throw new IllegalArgumentException (); 1004 } 1005 1006 public int getRowIndex() 1007 { 1008 return -1; 1009 } 1010 1011 public void setRowIndex(int i) 1012 { 1013 if (i < -1) 1014 throw new IllegalArgumentException (); 1015 } 1016 1017 public Object getWrappedData() 1018 { 1019 return null; 1020 } 1021 1022 public void setWrappedData(Object obj) 1023 { 1024 if (obj == null) 1025 return; throw new UnsupportedOperationException ( 1027 this.getClass().getName() + " UnsupportedOperationException"); 1028 } 1029 }; 1030 1031 private static class UIDataRowState implements Cloneable , Serializable 1032 { 1033 private HashMap _clientIdsToChildUIDataStates = new HashMap (); 1034 private HashMap _clientIdsToChildEVHStates = new HashMap (); 1035 1036 public UIDataRowState() 1037 { 1038 } 1039 1040 public UIDataRowState(UIDataRowState initial) 1041 { 1042 for (Iterator i = initial._clientIdsToChildEVHStates.entrySet().iterator(); i.hasNext();) 1043 { 1044 Map.Entry entry = (Map.Entry ) i.next(); 1045 EditableValueHolderState initialState = (EditableValueHolderState) entry.getValue(); 1046 _clientIdsToChildEVHStates.put(entry.getKey(), new EditableValueHolderState(initialState)); 1047 } 1048 1049 for (Iterator i = initial._clientIdsToChildUIDataStates.entrySet().iterator(); i.hasNext();) 1050 { 1051 Map.Entry entry = (Map.Entry ) i.next(); 1052 UIDataRowState initialState = (UIDataRowState) entry.getValue(); 1053 _clientIdsToChildEVHStates.put(entry.getKey(), new UIDataRowState(initialState)); 1054 } 1055 } 1056 } 1057 1058 private static class EditableValueHolderState implements Serializable 1059 { 1060 private Object _localValue; 1061 private boolean _localValueSet; 1062 private boolean _valid; 1063 private Object _submittedValue; 1064 1065 public EditableValueHolderState(EditableValueHolder vh) 1066 { 1067 _localValue = vh.getLocalValue(); 1068 _localValueSet = vh.isLocalValueSet(); 1069 _valid = vh.isValid(); 1070 _submittedValue = vh.getSubmittedValue(); 1071 } 1072 1073 public EditableValueHolderState(EditableValueHolderState state) 1074 { 1075 _localValue = state._localValue; 1076 _localValueSet = state._localValueSet; 1077 _valid = state._valid; 1078 _submittedValue = state._submittedValue; 1079 } 1080 1081 public void restore(EditableValueHolder vh) 1082 { 1083 vh.setValue(_localValue); 1084 vh.setLocalValueSet(_localValueSet); 1085 vh.setValid(_valid); 1086 vh.setSubmittedValue(_submittedValue); 1087 } 1088 1089 } 1090 1091 public void setValue(Object value) 1092 { 1093 _value = value; 1094 _dataModel = null; 1095 } 1096 1097 public Object saveState(FacesContext context) 1098 { 1099 Object [] values = new Object [STATE_SIZE]; 1100 values[SUPER_STATE_INDEX] = super.saveState(context); 1101 values[FIRST_INDEX] = _first; 1102 values[ROWS_INDEX] = _rows; 1103 values[VALUE_INDEX] = _value; 1104 values[VAR_INDEX] = _var; 1105 values[ROW_STATE_INDEX] = _rowState; 1106 return ((Object ) (values)); 1107 } 1108 1109 public void restoreState(FacesContext context, Object state) 1110 { 1111 Object [] values = (Object []) state; 1112 super.restoreState(context, values[0]); 1113 _first = (Integer ) values[FIRST_INDEX]; 1114 _rows = (Integer ) values[ROWS_INDEX]; 1115 _value = (Object ) values[VALUE_INDEX]; 1116 _var = (String ) values[VAR_INDEX]; 1117 _rowState = (UIDataRowState) values[ROW_STATE_INDEX]; 1118 1119 _firstTimeRendered = false; 1121 } 1122 1123 1125 public static final String COMPONENT_TYPE = "javax.faces.Data"; 1126 public static final String COMPONENT_FAMILY = "javax.faces.Data"; 1127 private static final String DEFAULT_RENDERER_TYPE = "javax.faces.Table"; 1128 private static final int DEFAULT_FIRST = 0; 1129 private static final int DEFAULT_ROWS = 0; 1130 1131 private Integer _first = null; 1132 private Integer _rows = null; 1133 private Object _value = null; 1134 1135 public UIData() 1136 { 1137 setRendererType(DEFAULT_RENDERER_TYPE); 1138 } 1139 1140 public String getFamily() 1141 { 1142 return COMPONENT_FAMILY; 1143 } 1144 1145 public void setFirst(int first) 1146 { 1147 _first = new Integer (first); 1148 } 1149 1150 public int getFirst() 1151 { 1152 if (_first != null) 1153 return _first.intValue(); 1154 ValueBinding vb = getValueBinding("first"); 1155 Integer v = vb != null ? (Integer ) vb.getValue(getFacesContext()) : null; 1156 return v != null ? v.intValue() : DEFAULT_FIRST; 1157 } 1158 1159 public int getRows() 1160 { 1161 if (_rows != null) 1162 return _rows.intValue(); 1163 ValueBinding vb = getValueBinding("rows"); 1164 Integer v = vb != null ? (Integer ) vb.getValue(getFacesContext()) : null; 1165 return v != null ? v.intValue() : DEFAULT_ROWS; 1166 } 1167 1168 public Object getValue() 1169 { 1170 if (_value != null) 1171 return _value; 1172 ValueBinding vb = getValueBinding("value"); 1173 return vb != null ? (Object ) vb.getValue(getFacesContext()) : null; 1174 } 1175 1176 1178} 1179 | Popular Tags |