OUtils.java
001 /*
002  * OUtils.java
003  *
004  *  Copyright (c) 1995-2012, The University of Sheffield. See the file
005  *  COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
006  *
007  *  This file is part of GATE (see http://gate.ac.uk/), and is free
008  *  software, licenced under the GNU Library General Public License,
009  *  Version 2, June 1991 (in the distribution as file licence.html,
010  *  and also available at http://gate.ac.uk/gate/licence.html).
011  *
012  *  Ian Roberts 05/05/2010
013  *
014  * $Id: OUtils.java 17530 2014-03-04 15:57:43Z markagreenwood $
015  *
016  * This class includes code from the com.hp.hpl.jena.util.URIref class of jena
017  * (http://jena.sourceforge.net) which is subject to the following licence:
018  *
019  *  (c) Copyright Hewlett-Packard Company 2001 
020  *  All rights reserved.
021  *
022  * Redistribution and use in source and binary forms, with or without
023  * modification, are permitted provided that the following conditions
024  * are met:
025  * 1. Redistributions of source code must retain the above copyright
026  *    notice, this list of conditions and the following disclaimer.
027  * 2. Redistributions in binary form must reproduce the above copyright
028  *    notice, this list of conditions and the following disclaimer in the
029  *    documentation and/or other materials provided with the distribution.
030  * 3. The name of the author may not be used to endorse or promote products
031  *    derived from this software without specific prior written permission.
032 
033  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
034  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
035  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
036  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
037  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
038  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
039  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
040  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
041  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
042  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
043  */
044 package gate.creole.ontology;
045 
046 import java.util.regex.Pattern;
047 
048 public class OUtils {
049 
050   /**
051    * Private constructor - this class should not be instantiated.
052    */
053   private OUtils() {
054   }
055 
056   /**
057    * Pattern for symbol and punctuation characters that are not recommended in
058    * URIs.
059    */
060   private static Pattern badPunctPattern =
061     Pattern.compile("[\\p{P}\\p{S}&&[^;\\?@&=\\+\\$,_\\.!~\\*\\(\\)\\-]]");
062 
063   /**
064    * Pattern matching runs of whitespace.
065    */
066   private static Pattern spacesPattern =
067     Pattern.compile("\\s+");
068 
069   /**
070    * Converts a string to a form suitable for use as a resource name by the
071    {@link Ontology#createOURIForName} method.  This is not a reversible
072    * encoding, but is intended to produce "readable" resource URIs in an
073    * ontology from English source strings.  The process is:
074    <ol>
075    <li>Replace any slashes, colons, apostrophes and other non-URI-legal
076    * punctuation characters and unicode symbol characters with a space, i.e.
077    * any punctuation except ; ? @ &amp; = + $ , - _ . ! ~ * ( )</li>
078    <li>Convert any runs of whitespace characters to a single underscore</li>
079    <li>{@link #uriEncode} the result.</li>
080    </ol>
081    * For example, this would convert "John Smith" to "John_Smith", "Allen &amp;
082    * Heath" to "Allen_&amp;_Heath", "N/A" to "N_A", "32 &#xb0;F" to "32_F", etc.
083    */
084   public static String toResourceName(String text) {
085     return uriEncode(spacesPattern.matcher(
086               badPunctPattern.matcher(text).replaceAll(" ")
087           ).replaceAll("_"));
088   }
089 
090   /**
091    * Convert a Unicode string (which is assumed to represent a URI or URI
092    * fragment) to an RFC 2396-compliant URI reference by first converting it to
093    * bytes in UTF-8 and then encoding the resulting bytes as specified by the
094    * RFC.  ASCII letters, numbers and the other characters that are permitted
095    * in URI references are left unchanged, existing %NN escape sequences are
096    * left unchanged, and any other characters are %-escaped as appropriate.  In
097    * particular any % characters in the original string that are not part of a
098    * %NN escape sequence will themselves be encoded as %25.
099    *
100    @param uriRef The uri, in characters specified by RFC 2396 + '#'
101    @return The corresponding Unicode String
102    */ 
103   @SuppressWarnings("fallthrough")
104   public static String uriEncode(String uriRef) {
105     try {
106       byte utf8[] = uriRef.getBytes("UTF-8");
107       byte rsltAscii[] new byte[utf8.length*6];
108       int in = 0;
109       int out = 0;
110       while in < utf8.length ) {  
111         switch utf8[in] ) {
112               case (byte)'a'case (byte)'b'case (byte)'c'case (byte)'d'case (byte)'e'case (byte)'f'case (byte)'g'case (byte)'h'case (byte)'i'case (byte)'j'case (byte)'k'case (byte)'l'case (byte)'m'case (byte)'n'case (byte)'o'case (byte)'p'case (byte)'q'case (byte)'r'case (byte)'s'case (byte)'t'case (byte)'u'case (byte)'v'case (byte)'w'case (byte)'x'case (byte)'y'case (byte)'z':
113               case (byte)'A'case (byte)'B'case (byte)'C'case (byte)'D'case (byte)'E'case (byte)'F'case (byte)'G'case (byte)'H'case (byte)'I'case (byte)'J'case (byte)'K'case (byte)'L'case (byte)'M'case (byte)'N'case (byte)'O'case (byte)'P'case (byte)'Q'case (byte)'R'case (byte)'S'case (byte)'T'case (byte)'U'case (byte)'V'case (byte)'W'case (byte)'X'case (byte)'Y'case (byte)'Z':
114               case (byte)'0'case (byte)'1'case (byte)'2'case (byte)'3'case (byte)'4'case (byte)'5'case (byte)'6'case (byte)'7'case (byte)'8'case (byte)'9':
115               case (byte)';'case (byte)'/'case (byte)'?'case (byte)':'case (byte)'@'case (byte)'&'case (byte)'='case (byte)'+'case (byte)'$'case (byte)',':
116               case (byte)'-'case (byte)'_'case (byte)'.'case (byte)'!'case (byte)'~'case (byte)'*'case (byte)'\''case (byte)'('case (byte)')':
117               case (byte)'#'
118               case (byte)'['case (byte)']':
119                   rsltAscii[out= utf8[in];
120                   out++;
121                   in++;
122                   break;
123               case (byte'%':
124                   try {
125                       if in+< utf8.length ) {
126                           byte first = hexEncode(hexDecode(utf8[in+1]));
127                           byte second = hexEncode(hexDecode(utf8[in+2]));
128                           rsltAscii[out++(byte)'%';
129                           rsltAscii[out++= first;
130                           rsltAscii[out++= second;
131                           in += 3;
132                           break;
133                       }
134                   }
135                   catch (IllegalArgumentException e) {
136                       // Illformed - should issue message ....
137                       //Original JENA class prints a warning here, we want to
138                       //ignore the error and simply encode bare % signs as %25
139                       //
140                       // Fall through.
141                   }
142               default:
143                       rsltAscii[out++(byte)'%';
144                       // Get rid of sign ...
145                       int c = utf8[in]&255;
146                       rsltAscii[out++= hexEncodec/16 );
147                       rsltAscii[out++= hexEncodec%16 );
148                       in++;
149                       break;
150           }
151       }
152       return new String(rsltAscii,0,out,"US-ASCII");
153     }
154     catch java.io.UnsupportedEncodingException e ) {
155         throw new Error"The JVM is required to support UTF-8 and US-ASCII encodings.");
156     }
157   }
158   
159   /**
160    * Convert a URI reference (URI or URI fragment), in US-ASCII, with escaped
161    * characters taken from UTF-8, to the corresponding Unicode string.
162    * On ill-formed input the results are undefined, specifically if
163    * the unescaped version is not a UTF-8 String, some String will be
164    * returned.
165    @param uri The uri, in characters specified by RFC 2396 + '#'.
166    @return The corresponding Unicode String.
167    @exception IllegalArgumentException If a % hex sequence is ill-formed.
168    */
169   public static String uriDecode(String uri) {
170       try {
171           byte ascii[] = uri.getBytes("US-ASCII");
172           byte utf8[] new byte[ascii.length];
173           int in = 0;
174           int out = 0;
175           while in < ascii.length ) {
176               // Original JENA class left escaped percent signs (%25)
177               // untouched, we convert them back to plain %
178               if ascii[in== (byte)'%' ) {
179                   in++;
180                   utf8[out++(byte)(hexDecode(ascii[in])*16 | hexDecode(ascii[in+1]));
181                   in += 2;
182               else {
183                   utf8[out++= ascii[in++];
184               }
185           }
186           return new String(utf8,0,out,"UTF-8");
187       }
188       catch java.io.UnsupportedEncodingException e ) {
189           throw new Error"The JVM is required to support UTF-8 and US-ASCII encodings.");
190       }
191       catch ArrayIndexOutOfBoundsException ee ) {
192           throw new IllegalArgumentException("Incomplete Hex escape sequence in " + uri );
193       }
194   }
195   
196   private static final byte hexEncode(int ) {
197       if (i<10)
198           return (byte) ('0' + i);
199       else
200           return (byte)('A' + i - 10);
201   }
202   
203   private static final int hexDecode(byte ) {
204       switch (b) { 
205           case (byte)'a'case (byte)'b'case (byte)'c'case (byte)'d'case (byte)'e'case (byte)'f':
206            return (b&255)-'a'+10;
207           case (byte)'A'case (byte)'B'case (byte)'C'case (byte)'D'case (byte)'E'case (byte)'F'
208           return b - (byte)'A' 10;
209           case (byte)'0'case (byte)'1'case (byte)'2'case (byte)'3'case (byte)'4'case (byte)'5'case (byte)'6'case (byte)'7'case (byte)'8'case (byte)'9':
210               return b - (byte)'0';
211               default:
212                   throw new IllegalArgumentException("Bad Hex escape character: " (b&255) );
213       }
214   }
215 }