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 StringBuilder result = new StringBuilder(); 171 172 if (renderCount != null) 173 { 174 result.append(renderCount); 175 } 176 177 if (renderCount != null || behaviorId != null) { 178 result.append(BEHAVIOR_INDEX_SEPARATOR); 179 } 180 181 if (behaviorId != null) 182 { 183 result.append(behaviorId); 184 } 185 result.append(SEPARATOR); 186 result.append(encodeComponentPath(componentPath)); 187 188 return result.toString(); 189 } 190 191 /** 192 * Method that rigidly checks if the string consists of digits only. 193 * 194 * @param string 195 * @return whether the string consists of digits only 196 */ 197 private static boolean isNumber(final String string) 198 { 199 if (string == null || string.isEmpty()) 200 { 201 return false; 202 } 203 for (int i = 0; i < string.length(); ++i) 204 { 205 if (!Character.isDigit(string.charAt(i))) 206 { 207 return false; 208 } 209 } 210 return true; 211 } 212 213 /** 214 * Parses the given string. 215 * 216 * @param string 217 * @return component info or <code>null</code> if the string is not in correct format. 218 */ 219 public static ComponentInfo parse(final String string) 220 { 221 if (Strings.isEmpty(string)) 222 { 223 return null; 224 } 225 int i = string.indexOf(SEPARATOR); 226 if (i == -1) 227 { 228 return null; 229 } 230 else 231 { 232 String listener = string.substring(0, i); 233 String componentPath = decodeComponentPath(string.substring(i + 1)); 234 235 Integer behaviorIndex = null; 236 Integer renderCount = null; 237 238 String listenerParts[] = Strings.split(listener, BEHAVIOR_INDEX_SEPARATOR); 239 if (listenerParts.length == 0) 240 { 241 return new ComponentInfo(renderCount, componentPath, behaviorIndex); 242 } 243 else if (listenerParts.length == 2) 244 { 245 if (isNumber(listenerParts[0])) 246 { 247 renderCount = Integer.valueOf(listenerParts[0]); 248 } 249 if (isNumber(listenerParts[1])) 250 { 251 behaviorIndex = Integer.valueOf(listenerParts[1]); 252 } 253 254 return new ComponentInfo(renderCount, componentPath, behaviorIndex); 255 } 256 else 257 { 258 return null; 259 } 260 } 261 } 262}