001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.request.mapper.info; 018 019import org.apache.wicket.util.lang.Args; 020import org.apache.wicket.util.string.Strings; 021 022/** 023 * Encodes listener and component path in form of 024 * {@code <listener>-<componentPath>}, 025 * {@code <listener>.<behaviorIndex>-<componentPath>} or 026 * {@code <render-count>.<listener>.<behaviorIndex>-<componentPath>} 027 * <p> 028 * Component path is escaped (':' characters are replaced by '~') 029 * 030 * @author Matej Knopp 031 */ 032public class ComponentInfo 033{ 034 private static final char BEHAVIOR_INDEX_SEPARATOR = '.'; 035 private static final char SEPARATOR = '-'; 036 private static final char COMPONENT_SEPARATOR = ':'; 037 private static final char SEPARATOR_ENCODED = '~'; 038 039 /** 040 * Replaces ':' with '-', and '-' with '~'. 041 * 042 * @param path 043 * the path to the component in its page 044 * @return the encoded path 045 */ 046 private static String encodeComponentPath(CharSequence path) 047 { 048 if (path != null) 049 { 050 int length = path.length(); 051 if (length == 0) 052 { 053 return path.toString(); 054 } 055 StringBuilder result = new StringBuilder(length); 056 for (int i = 0; i < length; i++) 057 { 058 char c = path.charAt(i); 059 switch (c) 060 { 061 case COMPONENT_SEPARATOR : 062 result.append(SEPARATOR); 063 break; 064 case SEPARATOR : 065 result.append(SEPARATOR_ENCODED); 066 break; 067 default : 068 result.append(c); 069 } 070 } 071 return result.toString(); 072 } 073 else 074 { 075 return null; 076 } 077 } 078 079 /** 080 * Replaces '~' with '-' and '-' with ':' 081 * 082 * @param path 083 * the encoded path of the component in its page 084 * @return the (non-encoded) path of the component in its page 085 */ 086 private static String decodeComponentPath(CharSequence path) 087 { 088 if (path != null) 089 { 090 int length = path.length(); 091 if (length == 0) 092 { 093 return path.toString(); 094 } 095 StringBuilder result = new StringBuilder(length); 096 for (int i = 0; i < length; i++) 097 { 098 char c = path.charAt(i); 099 switch (c) 100 { 101 case SEPARATOR_ENCODED : 102 result.append(SEPARATOR); 103 break; 104 case SEPARATOR : 105 result.append(COMPONENT_SEPARATOR); 106 break; 107 default : 108 result.append(c); 109 } 110 } 111 return result.toString(); 112 } 113 else 114 { 115 return null; 116 } 117 } 118 119 private final String componentPath; 120 private final Integer behaviorId; 121 private final Integer renderCount; 122 123 /** 124 * Construct. 125 * 126 * @param renderCount 127 * @param componentPath 128 * @param behaviorId 129 */ 130 public ComponentInfo(final Integer renderCount, final String componentPath, final Integer behaviorId) 131 { 132 Args.notNull(componentPath, "componentPath"); 133 134 this.componentPath = componentPath; 135 this.behaviorId = behaviorId; 136 this.renderCount = renderCount; 137 } 138 139 /** 140 * @return component path 141 */ 142 public String getComponentPath() 143 { 144 return componentPath; 145 } 146 147 /** 148 * @return behavior index 149 */ 150 public Integer getBehaviorId() 151 { 152 return behaviorId; 153 } 154 155 /** 156 * 157 * @return render count 158 */ 159 public Integer getRenderCount() 160 { 161 return renderCount; 162 } 163 164 /** 165 * @see java.lang.Object#toString() 166 */ 167 @Override 168 public String toString() 169 { 170 String path = encodeComponentPath(componentPath); 171 StringBuilder result = new StringBuilder(path.length() + 12); 172 173 if (renderCount != null) 174 { 175 result.append(renderCount); 176 } 177 178 if (renderCount != null || behaviorId != null) { 179 result.append(BEHAVIOR_INDEX_SEPARATOR); 180 } 181 182 if (behaviorId != null) 183 { 184 result.append(behaviorId); 185 } 186 result.append(SEPARATOR); 187 result.append(path); 188 189 return result.toString(); 190 } 191 192 /** 193 * Method that rigidly checks if the string consists of digits only. 194 * 195 * @param string 196 * @return whether the string consists of digits only 197 */ 198 private static boolean isNumber(final String string) 199 { 200 if (string == null || string.isEmpty()) 201 { 202 return false; 203 } 204 for (int i = 0; i < string.length(); ++i) 205 { 206 if (!Character.isDigit(string.charAt(i))) 207 { 208 return false; 209 } 210 } 211 return true; 212 } 213 214 /** 215 * Parses the given string. 216 * 217 * @param string 218 * @return component info or <code>null</code> if the string is not in correct format. 219 */ 220 public static ComponentInfo parse(final String string) 221 { 222 if (Strings.isEmpty(string)) 223 { 224 return null; 225 } 226 int i = string.indexOf(SEPARATOR); 227 if (i == -1) 228 { 229 return null; 230 } 231 else 232 { 233 String listener = string.substring(0, i); 234 String componentPath = decodeComponentPath(string.substring(i + 1)); 235 236 Integer behaviorIndex = null; 237 Integer renderCount = null; 238 239 String listenerParts[] = Strings.split(listener, BEHAVIOR_INDEX_SEPARATOR); 240 if (listenerParts.length == 0) 241 { 242 return new ComponentInfo(renderCount, componentPath, behaviorIndex); 243 } 244 else if (listenerParts.length == 2) 245 { 246 if (isNumber(listenerParts[0])) 247 { 248 renderCount = Integer.valueOf(listenerParts[0]); 249 } 250 if (isNumber(listenerParts[1])) 251 { 252 behaviorIndex = Integer.valueOf(listenerParts[1]); 253 } 254 255 return new ComponentInfo(renderCount, componentPath, behaviorIndex); 256 } 257 else 258 { 259 return null; 260 } 261 } 262 } 263}