├── .gitignore ├── README.txt ├── pom.xml └── src ├── main └── java │ └── com │ └── opera │ └── link │ └── apilib │ ├── ApiParameters.java │ ├── Base64.java │ ├── LinkClient.java │ ├── exceptions │ ├── LibOperaLinkException.java │ ├── LinkAccessDeniedException.java │ ├── LinkItemNotFound.java │ └── LinkResponseFormatException.java │ └── items │ ├── Bookmark.java │ ├── BookmarkFolder.java │ ├── BookmarkFolderEntry.java │ ├── BookmarkSeparator.java │ ├── Element.java │ ├── FolderContext.java │ ├── FolderEntry.java │ ├── FolderInterface.java │ ├── Note.java │ ├── NoteFolder.java │ ├── NoteFolderEntry.java │ ├── NoteSeparator.java │ ├── SearchEngine.java │ ├── SpeedDial.java │ └── UrlFilter.java └── test └── java └── com └── opera └── link └── api └── AppTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | target/ 3 | *.jar 4 | 5 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | ====================================================== 2 | JavaOperaLinkClient - Java Opera Link Client library 3 | ====================================================== 4 | 5 | 6 | Introduction 7 | ============ 8 | 9 | This is the Opera Link Public API client library for Java. 10 | It provides utilities to get and manipulate Opera Bookmarks, Notes and 11 | Speed Dials. The application which uses it should provide the library with an 12 | application key and secret key received from 13 | https://auth.opera.com/service/oauth. 14 | The library takes care of authorizing the user and giving easy access to get and 15 | modify his Opera Link data. 16 | 17 | Copyright and license 18 | ===================== 19 | 20 | The source code included in this distribution is released under the 21 | BSD license: 22 | 23 | Copyright © 2010, Opera Software 24 | All rights reserved. 25 | 26 | Redistribution and use in source and binary forms, with or without 27 | modification, are permitted provided that the following conditions are 28 | met: 29 | 30 | * Redistributions of source code must retain the above copyright 31 | notice, this list of conditions and the following disclaimer. 32 | * Redistributions in binary form must reproduce the above copyright 33 | notice, this list of conditions and the following disclaimer in the 34 | documentation and/or other materials provided with the distribution. 35 | * Neither the name of Opera Software nor the names of its contributors 36 | may be used to endorse or promote products derived from this 37 | software without specific prior written permission. 38 | 39 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 40 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 41 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 42 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 43 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 44 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 45 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 46 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 47 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 48 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 49 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 50 | 51 | Building 52 | ======== 53 | 54 | This project can easily be built using Maven. 55 | mvn install 56 | 57 | Will generate the opera-link-client.jar file in the target folder. And 58 | install it into the local maven repository. 59 | 60 | Usage description 61 | ================= 62 | 63 | LinkClient is a class which handles connection with Opera Link server. It 64 | supports user authorization and methods for getting and manipulating Opera Link 65 | elements. 66 | 67 | Before some data with bookmarks, notes or speeddials can be exchanged with 68 | server, the application must be granted access. To do so OAuth 1.0a protocol 69 | based authorization method is performed. The application must be registered 70 | at https://auth.opera.com/service/oauth/applications/ and have its consumerKey 71 | and consumerSecret. Register your app as a Web Application, this way you will 72 | be able to specify a callback URL. 73 | 74 | 1. Authorization 75 | ---------------- 76 | 77 | To authorize a new user LinkClient object must be created. It obtains 78 | a request token and generates the authorization website address where 79 | the user must be redirected. This example is taken from the 80 | AndroidNotes application 81 | (https://github.com/operasoftware/AndroidNotes): 82 | 83 | // create new connection 84 | link = new LinkClient(consumerKey, consumerSecret); 85 | 86 | // set the callback url, in this case the application has added a following data 87 | // specification to an intent filter in AndroidManifest.xml: 88 | // 89 | String callbackUrl = "notes://operalink.notes.com"; 90 | try { 91 | String authorizeUrl = link.getAuthorizationURL(callbackUrl); 92 | 93 | // create intent which will redirect the user to a browser 94 | Intent i = new Intent(Intent.ACTION_VIEW); 95 | i.setData(Uri.parse(authorizeUrl)); 96 | 97 | // redirect user to the authorization website: 98 | startActivityForResult(i, REDIRECTION_ACTIVITY); 99 | } catch (LibOperaLinkException e) { 100 | e.printStackTrace(); 101 | } 102 | 103 | After the user has granted access to the application and it has been resumed, 104 | authorization process can be finalized. The verification code is read from the 105 | intent data which was passed in the url query and then is used to obtain an access token. 106 | 107 | Uri uri = this.getIntent().getData(); 108 | if (uri == null) { 109 | return; 110 | } 111 | 112 | String verifier = uri.getQueryParameter(LinkClient.OAUTH_VERIFIER); 113 | try { 114 | 115 | // obtain access tokens 116 | link.grantAccess(verifier); 117 | 118 | // save access token and secret for this user 119 | accessToken = link.getAccessToken(); 120 | tokenSecret = link.getTokenSecret(); 121 | } catch (LibOperaLinkException e) { 122 | e.printStackTrace(); 123 | } 124 | 125 | When OAuth access token was once generated this line of code is equivalent: 126 | LinkClient link = LinkClient.createFromAccessToken(consumerKey, 127 | consumerSecret, accessToken, tokenSecret); 128 | 129 | 130 | 131 | 2. Accessing and manipulating data 132 | ---------------------------------- 133 | 134 | Use LinkClient object to get Opera Link data and to submit some changes to 135 | it. 136 | 137 | a) Bookmarks: 138 | This example shows how to access data, create a new folder and bookmark, how to move 139 | them around and finally how to delete them. 140 | 141 | // create and append a new sample folder to a root folder bookmarks list 142 | BookmarkFolder sample_folder = new BookmarkFolder('New folder'); 143 | link.add(sample_folder); 144 | 145 | // create and add a new bookmark to the sample folder 146 | Bookmark bookmark = new Bookmark("Opera Link", "http://link.opera.com/"); 147 | bookmark.visited = new Date(); 148 | link.addToFolder(bookmark, sample_folder); 149 | 150 | // now see that the elements were added - get all of the bookmarks from the 151 | // server 152 | ArrayList allBookmarks = link.getRootBookmarks(false); 153 | // and check if the last element is the sample folder added and it contains the 154 | // bookmark 155 | int bookmarksSize = allBookmarks.size(); 156 | assert(allBookmarks.get(bookmarksSize - 1).isFolder()); 157 | assert(allBookmarks.get(bookmarksSize - 1).title.equals('New folder')); 158 | sample_folder = (BookmarkFolder) allBookmarks.get(bookmarksSize - 1); 159 | assert(sample_folder.getChildren().get(0).isBookmark()); 160 | bookmark = (Bookmark) sample_folder.getChildren().get(0); 161 | assert(bookmark.title.equals('Opera Link')) ; 162 | 163 | // or just get from the server a content of the folder 164 | ArrayList sampleFolderContent = 165 | link.getBookmarksFromFolder(sample_folder, false); 166 | assert(sampleFolderContent.get(0).title.equals('Opera Link')) ; 167 | 168 | 169 | // Now let's reorder the bookmarks: 170 | // create another folder where already created folder and bookmark can be 171 | // moved into 172 | BookmarkFolder folderForMovedElements = new BookmarkFolder('Folder with moved elements'); 173 | link.add(folderForMovedElements); 174 | 175 | // move one folder into another 176 | link.moveInsideFolder(sample_folder, folderForMovedElements) 177 | 178 | // move the bookmark outside of sample folder and place it in a root folder 179 | // at the last position 180 | link.moveToRootFolder(bookmark); 181 | // or place it in a root folder before the added folder for moved elements 182 | link.moveBeforeElement(bookmark, folderForMovedElements); 183 | 184 | 185 | // Time to clean up: 186 | // trash added elements 187 | link.trash(bookmark); 188 | link.trash(sample_folder); 189 | link.trash(folderForMovedElements); 190 | // or delete them 191 | link.delete(bookmark); 192 | link.delete(sample_folder); 193 | link.delete(folderForMovedElements); 194 | 195 | 196 | b) Notes: 197 | Retrieving and manipulating notes data is analogical to bookmarks 198 | 199 | c) Speed dials: 200 | This example shows how to access, create, update and remove speed dials. In contrast to 201 | notes and bookmarks speed dials can not be moved. 202 | 203 | // create a new speeddial and add it at first position 204 | SpeedDial dial = new SpeedDial("http://opera.com/", "Opera Main Page", 1); 205 | link.add(dial); 206 | 207 | // send updates to a dial 208 | dial.title = "Opera"; 209 | link.update(dial); 210 | 211 | // access all of your speed dials 212 | ArrayList dials = link.getSpeedDials(); 213 | 214 | // delete the new added one: 215 | link.delete(dial); 216 | 217 | 218 | 3. Folder properties 219 | -------------------- 220 | 221 | BookmarkFolder and NoteFolder have some special properties you can access. 222 | Those are related to their special use on the devices, for example Opera 223 | Mini users can only access a folder for which isTargetMini method returns true. 224 | For more details see documentation. 225 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.opera.link.api 6 | opera-link-client 7 | 1.0-SNAPSHOT 8 | jar 9 | 10 | opera-link-client 11 | http://maven.apache.org 12 | 13 | 14 | UTF-8 15 | 16 | 17 | 18 | 19 | central 20 | Maven Repository Switchboard 21 | default 22 | http://repo1.maven.org/maven2 23 | 24 | false 25 | 26 | 27 | 28 | oauth 29 | http://oauth.googlecode.com/svn/code/maven 30 | 31 | 32 | 33 | 34 | 35 | commons-httpclient 36 | commons-httpclient 37 | 3.1 38 | 39 | 40 | commons-codec 41 | commons-codec 42 | 1.4 43 | 44 | 45 | commons-logging 46 | commons-logging 47 | 1.1.1 48 | 49 | 50 | net.oauth.core 51 | oauth-httpclient3 52 | 20090617 53 | 54 | 55 | net.oauth.core 56 | oauth 57 | 20100527 58 | 59 | 60 | net.oauth.core 61 | oauth-consumer 62 | 20100527 63 | 64 | 65 | org.json 66 | json 67 | 20090211 68 | 69 | 70 | junit 71 | junit 72 | 3.8.1 73 | test 74 | 75 | 76 | 77 | opera-link-client 78 | 79 | 80 | org.apache.maven.plugins 81 | maven-compiler-plugin 82 | 83 | 1.6 84 | 1.6 85 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /src/main/java/com/opera/link/apilib/ApiParameters.java: -------------------------------------------------------------------------------- 1 | package com.opera.link.apilib; 2 | 3 | public class ApiParameters { 4 | 5 | public static final String API_METHOD_PARAM = "api_method"; 6 | public static final String API_OUTPUT_PARAM = "api_output"; 7 | public static final String JSON_OUTPUT_PARAM = "json"; 8 | 9 | public static final String CREATE = "create"; 10 | public static final String UPDATE = "update"; 11 | public static final String DELETE = "delete"; 12 | public static final String TRASH = "trash"; 13 | public static final String MOVE = "move"; 14 | 15 | public static final String MOVE_POSITION_INTO = "inside"; 16 | public static final String MOVE_POSITION_AFTER = "after"; 17 | public static final String MOVE_POSITION_BEFORE = "before"; 18 | public static final String MOVE_REFERENCE_ITEM_PARAM = "reference_item"; 19 | public static final String MOVE_RELATIVE_POSITION_PARAM = "relative_position"; 20 | 21 | public static final String URL_GET_CHILDREN_PARAM = "children/"; 22 | public static final String URL_GET_DESCENDANTS_PARAM = "descendants/"; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/opera/link/apilib/Base64.java: -------------------------------------------------------------------------------- 1 | package com.opera.link.apilib; 2 | 3 | /** 4 | *

Encodes and decodes to and from Base64 notation.

5 | *

Homepage: http://iharder.net/base64.

6 | * 7 | *

Example:

8 | * 9 | * String encoded = Base64.encode( myByteArray ); 10 | *
11 | * byte[] myByteArray = Base64.decode( encoded ); 12 | * 13 | *

The options parameter, which appears in a few places, is used to pass 14 | * several pieces of information to the encoder. In the "higher level" methods such as 15 | * encodeBytes( bytes, options ) the options parameter can be used to indicate such 16 | * things as first gzipping the bytes before encoding them, not inserting linefeeds, 17 | * and encoding using the URL-safe and Ordered dialects.

18 | * 19 | *

Note, according to RFC3548, 20 | * Section 2.1, implementations should not add line feeds unless explicitly told 21 | * to do so. I've got Base64 set to this behavior now, although earlier versions 22 | * broke lines by default.

23 | * 24 | *

The constants defined in Base64 can be OR-ed together to combine options, so you 25 | * might make a call like this:

26 | * 27 | * String encoded = Base64.encodeBytes( mybytes, Base64.GZIP | Base64.DO_BREAK_LINES ); 28 | *

to compress the data before encoding it and then making the output have newline characters.

29 | *

Also...

30 | * String encoded = Base64.encodeBytes( crazyString.getBytes() ); 31 | * 32 | * 33 | * 34 | *

35 | * Change Log: 36 | *

37 | *
    38 | *
  • v2.3.7 - Fixed subtle bug when base 64 input stream contained the 39 | * value 01111111, which is an invalid base 64 character but should not 40 | * throw an ArrayIndexOutOfBoundsException either. Led to discovery of 41 | * mishandling (or potential for better handling) of other bad input 42 | * characters. You should now get an IOException if you try decoding 43 | * something that has bad characters in it.
  • 44 | *
  • v2.3.6 - Fixed bug when breaking lines and the final byte of the encoded 45 | * string ended in the last column; the buffer was not properly shrunk and 46 | * contained an extra (null) byte that made it into the string.
  • 47 | *
  • v2.3.5 - Fixed bug in {@link #encodeFromFile} where estimated buffer size 48 | * was wrong for files of size 31, 34, and 37 bytes.
  • 49 | *
  • v2.3.4 - Fixed bug when working with gzipped streams whereby flushing 50 | * the Base64.OutputStream closed the Base64 encoding (by padding with equals 51 | * signs) too soon. Also added an option to suppress the automatic decoding 52 | * of gzipped streams. Also added experimental support for specifying a 53 | * class loader when using the 54 | * {@link #decodeToObject(java.lang.String, int, java.lang.ClassLoader)} 55 | * method.
  • 56 | *
  • v2.3.3 - Changed default char encoding to US-ASCII which reduces the internal Java 57 | * footprint with its CharEncoders and so forth. Fixed some javadocs that were 58 | * inconsistent. Removed imports and specified things like java.io.IOException 59 | * explicitly inline.
  • 60 | *
  • v2.3.2 - Reduced memory footprint! Finally refined the "guessing" of how big the 61 | * final encoded data will be so that the code doesn't have to create two output 62 | * arrays: an oversized initial one and then a final, exact-sized one. Big win 63 | * when using the {@link #encodeBytesToBytes(byte[])} family of methods (and not 64 | * using the gzip options which uses a different mechanism with streams and stuff).
  • 65 | *
  • v2.3.1 - Added {@link #encodeBytesToBytes(byte[], int, int, int)} and some 66 | * similar helper methods to be more efficient with memory by not returning a 67 | * String but just a byte array.
  • 68 | *
  • v2.3 - This is not a drop-in replacement! This is two years of comments 69 | * and bug fixes queued up and finally executed. Thanks to everyone who sent 70 | * me stuff, and I'm sorry I wasn't able to distribute your fixes to everyone else. 71 | * Much bad coding was cleaned up including throwing exceptions where necessary 72 | * instead of returning null values or something similar. Here are some changes 73 | * that may affect you: 74 | *
      75 | *
    • Does not break lines, by default. This is to keep in compliance with 76 | * RFC3548.
    • 77 | *
    • Throws exceptions instead of returning null values. Because some operations 78 | * (especially those that may permit the GZIP option) use IO streams, there 79 | * is a possiblity of an java.io.IOException being thrown. After some discussion and 80 | * thought, I've changed the behavior of the methods to throw java.io.IOExceptions 81 | * rather than return null if ever there's an error. I think this is more 82 | * appropriate, though it will require some changes to your code. Sorry, 83 | * it should have been done this way to begin with.
    • 84 | *
    • Removed all references to System.out, System.err, and the like. 85 | * Shame on me. All I can say is sorry they were ever there.
    • 86 | *
    • Throws NullPointerExceptions and IllegalArgumentExceptions as needed 87 | * such as when passed arrays are null or offsets are invalid.
    • 88 | *
    • Cleaned up as much javadoc as I could to avoid any javadoc warnings. 89 | * This was especially annoying before for people who were thorough in their 90 | * own projects and then had gobs of javadoc warnings on this file.
    • 91 | *
    92 | *
  • v2.2.1 - Fixed bug using URL_SAFE and ORDERED encodings. Fixed bug 93 | * when using very small files (~< 40 bytes).
  • 94 | *
  • v2.2 - Added some helper methods for encoding/decoding directly from 95 | * one file to the next. Also added a main() method to support command line 96 | * encoding/decoding from one file to the next. Also added these Base64 dialects: 97 | *
      98 | *
    1. The default is RFC3548 format.
    2. 99 | *
    3. Calling Base64.setFormat(Base64.BASE64_FORMAT.URLSAFE_FORMAT) generates 100 | * URL and file name friendly format as described in Section 4 of RFC3548. 101 | * http://www.faqs.org/rfcs/rfc3548.html
    4. 102 | *
    5. Calling Base64.setFormat(Base64.BASE64_FORMAT.ORDERED_FORMAT) generates 103 | * URL and file name friendly format that preserves lexical ordering as described 104 | * in http://www.faqs.org/qa/rfcc-1940.html
    6. 105 | *
    106 | * Special thanks to Jim Kellerman at http://www.powerset.com/ 107 | * for contributing the new Base64 dialects. 108 | *
  • 109 | * 110 | *
  • v2.1 - Cleaned up javadoc comments and unused variables and methods. Added 111 | * some convenience methods for reading and writing to and from files.
  • 112 | *
  • v2.0.2 - Now specifies UTF-8 encoding in places where the code fails on systems 113 | * with other encodings (like EBCDIC).
  • 114 | *
  • v2.0.1 - Fixed an error when decoding a single byte, that is, when the 115 | * encoded data was a single byte.
  • 116 | *
  • v2.0 - I got rid of methods that used booleans to set options. 117 | * Now everything is more consolidated and cleaner. The code now detects 118 | * when data that's being decoded is gzip-compressed and will decompress it 119 | * automatically. Generally things are cleaner. You'll probably have to 120 | * change some method calls that you were making to support the new 121 | * options format (ints that you "OR" together).
  • 122 | *
  • v1.5.1 - Fixed bug when decompressing and decoding to a 123 | * byte[] using decode( String s, boolean gzipCompressed ). 124 | * Added the ability to "suspend" encoding in the Output Stream so 125 | * you can turn on and off the encoding if you need to embed base64 126 | * data in an otherwise "normal" stream (like an XML file).
  • 127 | *
  • v1.5 - Output stream pases on flush() command but doesn't do anything itself. 128 | * This helps when using GZIP streams. 129 | * Added the ability to GZip-compress objects before encoding them.
  • 130 | *
  • v1.4 - Added helper methods to read/write files.
  • 131 | *
  • v1.3.6 - Fixed OutputStream.flush() so that 'position' is reset.
  • 132 | *
  • v1.3.5 - Added flag to turn on and off line breaks. Fixed bug in input stream 133 | * where last buffer being read, if not completely full, was not returned.
  • 134 | *
  • v1.3.4 - Fixed when "improperly padded stream" error was thrown at the wrong time.
  • 135 | *
  • v1.3.3 - Fixed I/O streams which were totally messed up.
  • 136 | *
137 | * 138 | *

139 | * I am placing this code in the Public Domain. Do with it as you will. 140 | * This software comes with no guarantees or warranties but with 141 | * plenty of well-wishing instead! 142 | * Please visit http://iharder.net/base64 143 | * periodically to check for updates or to contribute improvements. 144 | *

145 | * 146 | * @author Robert Harder 147 | * @author rob@iharder.net 148 | * @version 2.3.7 149 | */ 150 | public class Base64 151 | { 152 | 153 | /* ******** P U B L I C F I E L D S ******** */ 154 | 155 | 156 | /** No options specified. Value is zero. */ 157 | public final static int NO_OPTIONS = 0; 158 | 159 | /** Specify encoding in first bit. Value is one. */ 160 | public final static int ENCODE = 1; 161 | 162 | 163 | /** Specify decoding in first bit. Value is zero. */ 164 | public final static int DECODE = 0; 165 | 166 | 167 | /** Specify that data should be gzip-compressed in second bit. Value is two. */ 168 | public final static int GZIP = 2; 169 | 170 | /** Specify that gzipped data should not be automatically gunzipped. */ 171 | public final static int DONT_GUNZIP = 4; 172 | 173 | 174 | /** Do break lines when encoding. Value is 8. */ 175 | public final static int DO_BREAK_LINES = 8; 176 | 177 | /** 178 | * Encode using Base64-like encoding that is URL- and Filename-safe as described 179 | * in Section 4 of RFC3548: 180 | * http://www.faqs.org/rfcs/rfc3548.html. 181 | * It is important to note that data encoded this way is not officially valid Base64, 182 | * or at the very least should not be called Base64 without also specifying that is 183 | * was encoded using the URL- and Filename-safe dialect. 184 | */ 185 | public final static int URL_SAFE = 16; 186 | 187 | 188 | /** 189 | * Encode using the special "ordered" dialect of Base64 described here: 190 | * http://www.faqs.org/qa/rfcc-1940.html. 191 | */ 192 | public final static int ORDERED = 32; 193 | 194 | 195 | /* ******** P R I V A T E F I E L D S ******** */ 196 | 197 | 198 | /** Maximum line length (76) of Base64 output. */ 199 | private final static int MAX_LINE_LENGTH = 76; 200 | 201 | 202 | /** The equals sign (=) as a byte. */ 203 | private final static byte EQUALS_SIGN = (byte)'='; 204 | 205 | 206 | /** The new line character (\n) as a byte. */ 207 | private final static byte NEW_LINE = (byte)'\n'; 208 | 209 | 210 | /** Preferred encoding. */ 211 | private final static String PREFERRED_ENCODING = "US-ASCII"; 212 | 213 | 214 | private final static byte WHITE_SPACE_ENC = -5; // Indicates white space in encoding 215 | private final static byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in encoding 216 | 217 | 218 | /* ******** S T A N D A R D B A S E 6 4 A L P H A B E T ******** */ 219 | 220 | /** The 64 valid Base64 values. */ 221 | /* Host platform me be something funny like EBCDIC, so we hardcode these values. */ 222 | private final static byte[] _STANDARD_ALPHABET = { 223 | (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', 224 | (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', 225 | (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', 226 | (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', 227 | (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', 228 | (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', 229 | (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', 230 | (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z', 231 | (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', 232 | (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'+', (byte)'/' 233 | }; 234 | 235 | 236 | /** 237 | * Translates a Base64 value to either its 6-bit reconstruction value 238 | * or a negative number indicating some other meaning. 239 | **/ 240 | private final static byte[] _STANDARD_DECODABET = { 241 | -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 242 | -5,-5, // Whitespace: Tab and Linefeed 243 | -9,-9, // Decimal 11 - 12 244 | -5, // Whitespace: Carriage Return 245 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 246 | -9,-9,-9,-9,-9, // Decimal 27 - 31 247 | -5, // Whitespace: Space 248 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 249 | 62, // Plus sign at decimal 43 250 | -9,-9,-9, // Decimal 44 - 46 251 | 63, // Slash at decimal 47 252 | 52,53,54,55,56,57,58,59,60,61, // Numbers zero through nine 253 | -9,-9,-9, // Decimal 58 - 60 254 | -1, // Equals sign at decimal 61 255 | -9,-9,-9, // Decimal 62 - 64 256 | 0,1,2,3,4,5,6,7,8,9,10,11,12,13, // Letters 'A' through 'N' 257 | 14,15,16,17,18,19,20,21,22,23,24,25, // Letters 'O' through 'Z' 258 | -9,-9,-9,-9,-9,-9, // Decimal 91 - 96 259 | 26,27,28,29,30,31,32,33,34,35,36,37,38, // Letters 'a' through 'm' 260 | 39,40,41,42,43,44,45,46,47,48,49,50,51, // Letters 'n' through 'z' 261 | -9,-9,-9,-9,-9 // Decimal 123 - 127 262 | ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 263 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 264 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 265 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 266 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 267 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 268 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 269 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 270 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 271 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 272 | }; 273 | 274 | 275 | /* ******** U R L S A F E B A S E 6 4 A L P H A B E T ******** */ 276 | 277 | /** 278 | * Used in the URL- and Filename-safe dialect described in Section 4 of RFC3548: 279 | * http://www.faqs.org/rfcs/rfc3548.html. 280 | * Notice that the last two bytes become "hyphen" and "underscore" instead of "plus" and "slash." 281 | */ 282 | private final static byte[] _URL_SAFE_ALPHABET = { 283 | (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', 284 | (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', 285 | (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', 286 | (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', 287 | (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', 288 | (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', 289 | (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', 290 | (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z', 291 | (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', 292 | (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'-', (byte)'_' 293 | }; 294 | 295 | /** 296 | * Used in decoding URL- and Filename-safe dialects of Base64. 297 | */ 298 | private final static byte[] _URL_SAFE_DECODABET = { 299 | -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 300 | -5,-5, // Whitespace: Tab and Linefeed 301 | -9,-9, // Decimal 11 - 12 302 | -5, // Whitespace: Carriage Return 303 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 304 | -9,-9,-9,-9,-9, // Decimal 27 - 31 305 | -5, // Whitespace: Space 306 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 307 | -9, // Plus sign at decimal 43 308 | -9, // Decimal 44 309 | 62, // Minus sign at decimal 45 310 | -9, // Decimal 46 311 | -9, // Slash at decimal 47 312 | 52,53,54,55,56,57,58,59,60,61, // Numbers zero through nine 313 | -9,-9,-9, // Decimal 58 - 60 314 | -1, // Equals sign at decimal 61 315 | -9,-9,-9, // Decimal 62 - 64 316 | 0,1,2,3,4,5,6,7,8,9,10,11,12,13, // Letters 'A' through 'N' 317 | 14,15,16,17,18,19,20,21,22,23,24,25, // Letters 'O' through 'Z' 318 | -9,-9,-9,-9, // Decimal 91 - 94 319 | 63, // Underscore at decimal 95 320 | -9, // Decimal 96 321 | 26,27,28,29,30,31,32,33,34,35,36,37,38, // Letters 'a' through 'm' 322 | 39,40,41,42,43,44,45,46,47,48,49,50,51, // Letters 'n' through 'z' 323 | -9,-9,-9,-9,-9 // Decimal 123 - 127 324 | ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 325 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 326 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 327 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 328 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 329 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 330 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 331 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 332 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 333 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 334 | }; 335 | 336 | 337 | 338 | /* ******** O R D E R E D B A S E 6 4 A L P H A B E T ******** */ 339 | 340 | /** 341 | * I don't get the point of this technique, but someone requested it, 342 | * and it is described here: 343 | * http://www.faqs.org/qa/rfcc-1940.html. 344 | */ 345 | private final static byte[] _ORDERED_ALPHABET = { 346 | (byte)'-', 347 | (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', 348 | (byte)'5', (byte)'6', (byte)'7', (byte)'8', (byte)'9', 349 | (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', 350 | (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', 351 | (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', 352 | (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', 353 | (byte)'_', 354 | (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', 355 | (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', 356 | (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', 357 | (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z' 358 | }; 359 | 360 | /** 361 | * Used in decoding the "ordered" dialect of Base64. 362 | */ 363 | private final static byte[] _ORDERED_DECODABET = { 364 | -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 365 | -5,-5, // Whitespace: Tab and Linefeed 366 | -9,-9, // Decimal 11 - 12 367 | -5, // Whitespace: Carriage Return 368 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 369 | -9,-9,-9,-9,-9, // Decimal 27 - 31 370 | -5, // Whitespace: Space 371 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 372 | -9, // Plus sign at decimal 43 373 | -9, // Decimal 44 374 | 0, // Minus sign at decimal 45 375 | -9, // Decimal 46 376 | -9, // Slash at decimal 47 377 | 1,2,3,4,5,6,7,8,9,10, // Numbers zero through nine 378 | -9,-9,-9, // Decimal 58 - 60 379 | -1, // Equals sign at decimal 61 380 | -9,-9,-9, // Decimal 62 - 64 381 | 11,12,13,14,15,16,17,18,19,20,21,22,23, // Letters 'A' through 'M' 382 | 24,25,26,27,28,29,30,31,32,33,34,35,36, // Letters 'N' through 'Z' 383 | -9,-9,-9,-9, // Decimal 91 - 94 384 | 37, // Underscore at decimal 95 385 | -9, // Decimal 96 386 | 38,39,40,41,42,43,44,45,46,47,48,49,50, // Letters 'a' through 'm' 387 | 51,52,53,54,55,56,57,58,59,60,61,62,63, // Letters 'n' through 'z' 388 | -9,-9,-9,-9,-9 // Decimal 123 - 127 389 | ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 390 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 391 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 392 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 393 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 394 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 395 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 396 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 397 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 398 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 399 | }; 400 | 401 | 402 | /* ******** D E T E R M I N E W H I C H A L H A B E T ******** */ 403 | 404 | 405 | /** 406 | * Returns one of the _SOMETHING_ALPHABET byte arrays depending on 407 | * the options specified. 408 | * It's possible, though silly, to specify ORDERED and URLSAFE 409 | * in which case one of them will be picked, though there is 410 | * no guarantee as to which one will be picked. 411 | */ 412 | private final static byte[] getAlphabet( int options ) { 413 | if ((options & URL_SAFE) == URL_SAFE) { 414 | return _URL_SAFE_ALPHABET; 415 | } else if ((options & ORDERED) == ORDERED) { 416 | return _ORDERED_ALPHABET; 417 | } else { 418 | return _STANDARD_ALPHABET; 419 | } 420 | } // end getAlphabet 421 | 422 | 423 | /** 424 | * Returns one of the _SOMETHING_DECODABET byte arrays depending on 425 | * the options specified. 426 | * It's possible, though silly, to specify ORDERED and URL_SAFE 427 | * in which case one of them will be picked, though there is 428 | * no guarantee as to which one will be picked. 429 | */ 430 | private final static byte[] getDecodabet( int options ) { 431 | if( (options & URL_SAFE) == URL_SAFE) { 432 | return _URL_SAFE_DECODABET; 433 | } else if ((options & ORDERED) == ORDERED) { 434 | return _ORDERED_DECODABET; 435 | } else { 436 | return _STANDARD_DECODABET; 437 | } 438 | } // end getAlphabet 439 | 440 | 441 | 442 | /** Defeats instantiation. */ 443 | private Base64(){} 444 | 445 | 446 | 447 | 448 | /* ******** E N C O D I N G M E T H O D S ******** */ 449 | 450 | 451 | /** 452 | * Encodes up to the first three bytes of array threeBytes 453 | * and returns a four-byte array in Base64 notation. 454 | * The actual number of significant bytes in your array is 455 | * given by numSigBytes. 456 | * The array threeBytes needs only be as big as 457 | * numSigBytes. 458 | * Code can reuse a byte array by passing a four-byte array as b4. 459 | * 460 | * @param b4 A reusable byte array to reduce array instantiation 461 | * @param threeBytes the array to convert 462 | * @param numSigBytes the number of significant bytes in your array 463 | * @return four byte array in Base64 notation. 464 | * @since 1.5.1 465 | */ 466 | private static byte[] encode3to4( byte[] b4, byte[] threeBytes, int numSigBytes, int options ) { 467 | encode3to4( threeBytes, 0, numSigBytes, b4, 0, options ); 468 | return b4; 469 | } // end encode3to4 470 | 471 | 472 | /** 473 | *

Encodes up to three bytes of the array source 474 | * and writes the resulting four Base64 bytes to destination. 475 | * The source and destination arrays can be manipulated 476 | * anywhere along their length by specifying 477 | * srcOffset and destOffset. 478 | * This method does not check to make sure your arrays 479 | * are large enough to accomodate srcOffset + 3 for 480 | * the source array or destOffset + 4 for 481 | * the destination array. 482 | * The actual number of significant bytes in your array is 483 | * given by numSigBytes.

484 | *

This is the lowest level of the encoding methods with 485 | * all possible parameters.

486 | * 487 | * @param source the array to convert 488 | * @param srcOffset the index where conversion begins 489 | * @param numSigBytes the number of significant bytes in your array 490 | * @param destination the array to hold the conversion 491 | * @param destOffset the index where output will be put 492 | * @return the destination array 493 | * @since 1.3 494 | */ 495 | private static byte[] encode3to4( 496 | byte[] source, int srcOffset, int numSigBytes, 497 | byte[] destination, int destOffset, int options ) { 498 | 499 | byte[] ALPHABET = getAlphabet( options ); 500 | 501 | // 1 2 3 502 | // 01234567890123456789012345678901 Bit position 503 | // --------000000001111111122222222 Array position from threeBytes 504 | // --------| || || || | Six bit groups to index ALPHABET 505 | // >>18 >>12 >> 6 >> 0 Right shift necessary 506 | // 0x3f 0x3f 0x3f Additional AND 507 | 508 | // Create buffer with zero-padding if there are only one or two 509 | // significant bytes passed in the array. 510 | // We have to shift left 24 in order to flush out the 1's that appear 511 | // when Java treats a value as negative that is cast from a byte to an int. 512 | int inBuff = ( numSigBytes > 0 ? ((source[ srcOffset ] << 24) >>> 8) : 0 ) 513 | | ( numSigBytes > 1 ? ((source[ srcOffset + 1 ] << 24) >>> 16) : 0 ) 514 | | ( numSigBytes > 2 ? ((source[ srcOffset + 2 ] << 24) >>> 24) : 0 ); 515 | 516 | switch( numSigBytes ) 517 | { 518 | case 3: 519 | destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; 520 | destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; 521 | destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ]; 522 | destination[ destOffset + 3 ] = ALPHABET[ (inBuff ) & 0x3f ]; 523 | return destination; 524 | 525 | case 2: 526 | destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; 527 | destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; 528 | destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ]; 529 | destination[ destOffset + 3 ] = EQUALS_SIGN; 530 | return destination; 531 | 532 | case 1: 533 | destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; 534 | destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; 535 | destination[ destOffset + 2 ] = EQUALS_SIGN; 536 | destination[ destOffset + 3 ] = EQUALS_SIGN; 537 | return destination; 538 | 539 | default: 540 | return destination; 541 | } // end switch 542 | } // end encode3to4 543 | 544 | 545 | 546 | /** 547 | * Performs Base64 encoding on the raw ByteBuffer, 548 | * writing it to the encoded ByteBuffer. 549 | * This is an experimental feature. Currently it does not 550 | * pass along any options (such as {@link #DO_BREAK_LINES} 551 | * or {@link #GZIP}. 552 | * 553 | * @param raw input buffer 554 | * @param encoded output buffer 555 | * @since 2.3 556 | */ 557 | public static void encode( java.nio.ByteBuffer raw, java.nio.ByteBuffer encoded ){ 558 | byte[] raw3 = new byte[3]; 559 | byte[] enc4 = new byte[4]; 560 | 561 | while( raw.hasRemaining() ){ 562 | int rem = Math.min(3,raw.remaining()); 563 | raw.get(raw3,0,rem); 564 | Base64.encode3to4(enc4, raw3, rem, Base64.NO_OPTIONS ); 565 | encoded.put(enc4); 566 | } // end input remaining 567 | } 568 | 569 | 570 | /** 571 | * Performs Base64 encoding on the raw ByteBuffer, 572 | * writing it to the encoded CharBuffer. 573 | * This is an experimental feature. Currently it does not 574 | * pass along any options (such as {@link #DO_BREAK_LINES} 575 | * or {@link #GZIP}. 576 | * 577 | * @param raw input buffer 578 | * @param encoded output buffer 579 | * @since 2.3 580 | */ 581 | public static void encode( java.nio.ByteBuffer raw, java.nio.CharBuffer encoded ){ 582 | byte[] raw3 = new byte[3]; 583 | byte[] enc4 = new byte[4]; 584 | 585 | while( raw.hasRemaining() ){ 586 | int rem = Math.min(3,raw.remaining()); 587 | raw.get(raw3,0,rem); 588 | Base64.encode3to4(enc4, raw3, rem, Base64.NO_OPTIONS ); 589 | for( int i = 0; i < 4; i++ ){ 590 | encoded.put( (char)(enc4[i] & 0xFF) ); 591 | } 592 | } // end input remaining 593 | } 594 | 595 | 596 | 597 | 598 | /** 599 | * Serializes an object and returns the Base64-encoded 600 | * version of that serialized object. 601 | * 602 | *

As of v 2.3, if the object 603 | * cannot be serialized or there is another error, 604 | * the method will throw an java.io.IOException. This is new to v2.3! 605 | * In earlier versions, it just returned a null value, but 606 | * in retrospect that's a pretty poor way to handle it.

607 | * 608 | * The object is not GZip-compressed before being encoded. 609 | * 610 | * @param serializableObject The object to encode 611 | * @return The Base64-encoded object 612 | * @throws java.io.IOException if there is an error 613 | * @throws NullPointerException if serializedObject is null 614 | * @since 1.4 615 | */ 616 | public static String encodeObject( java.io.Serializable serializableObject ) 617 | throws java.io.IOException { 618 | return encodeObject( serializableObject, NO_OPTIONS ); 619 | } // end encodeObject 620 | 621 | 622 | 623 | /** 624 | * Serializes an object and returns the Base64-encoded 625 | * version of that serialized object. 626 | * 627 | *

As of v 2.3, if the object 628 | * cannot be serialized or there is another error, 629 | * the method will throw an java.io.IOException. This is new to v2.3! 630 | * In earlier versions, it just returned a null value, but 631 | * in retrospect that's a pretty poor way to handle it.

632 | * 633 | * The object is not GZip-compressed before being encoded. 634 | *

635 | * Example options:

 636 |      *   GZIP: gzip-compresses object before encoding it.
 637 |      *   DO_BREAK_LINES: break lines at 76 characters
 638 |      * 
639 | *

640 | * Example: encodeObject( myObj, Base64.GZIP ) or 641 | *

642 | * Example: encodeObject( myObj, Base64.GZIP | Base64.DO_BREAK_LINES ) 643 | * 644 | * @param serializableObject The object to encode 645 | * @param options Specified options 646 | * @return The Base64-encoded object 647 | * @see Base64#GZIP 648 | * @see Base64#DO_BREAK_LINES 649 | * @throws java.io.IOException if there is an error 650 | * @since 2.0 651 | */ 652 | public static String encodeObject( java.io.Serializable serializableObject, int options ) 653 | throws java.io.IOException { 654 | 655 | if( serializableObject == null ){ 656 | throw new NullPointerException( "Cannot serialize a null object." ); 657 | } // end if: null 658 | 659 | // Streams 660 | java.io.ByteArrayOutputStream baos = null; 661 | java.io.OutputStream b64os = null; 662 | java.util.zip.GZIPOutputStream gzos = null; 663 | java.io.ObjectOutputStream oos = null; 664 | 665 | 666 | try { 667 | // ObjectOutputStream -> (GZIP) -> Base64 -> ByteArrayOutputStream 668 | baos = new java.io.ByteArrayOutputStream(); 669 | b64os = new Base64.OutputStream( baos, ENCODE | options ); 670 | if( (options & GZIP) != 0 ){ 671 | // Gzip 672 | gzos = new java.util.zip.GZIPOutputStream(b64os); 673 | oos = new java.io.ObjectOutputStream( gzos ); 674 | } else { 675 | // Not gzipped 676 | oos = new java.io.ObjectOutputStream( b64os ); 677 | } 678 | oos.writeObject( serializableObject ); 679 | } // end try 680 | catch( java.io.IOException e ) { 681 | // Catch it and then throw it immediately so that 682 | // the finally{} block is called for cleanup. 683 | throw e; 684 | } // end catch 685 | finally { 686 | try{ oos.close(); } catch( Exception e ){} 687 | try{ gzos.close(); } catch( Exception e ){} 688 | try{ b64os.close(); } catch( Exception e ){} 689 | try{ baos.close(); } catch( Exception e ){} 690 | } // end finally 691 | 692 | // Return value according to relevant encoding. 693 | try { 694 | return new String( baos.toByteArray(), PREFERRED_ENCODING ); 695 | } // end try 696 | catch (java.io.UnsupportedEncodingException uue){ 697 | // Fall back to some Java default 698 | return new String( baos.toByteArray() ); 699 | } // end catch 700 | 701 | } // end encode 702 | 703 | 704 | 705 | /** 706 | * Encodes a byte array into Base64 notation. 707 | * Does not GZip-compress data. 708 | * 709 | * @param source The data to convert 710 | * @return The data in Base64-encoded form 711 | * @throws NullPointerException if source array is null 712 | * @since 1.4 713 | */ 714 | public static String encodeBytes( byte[] source ) { 715 | // Since we're not going to have the GZIP encoding turned on, 716 | // we're not going to have an java.io.IOException thrown, so 717 | // we should not force the user to have to catch it. 718 | String encoded = null; 719 | try { 720 | encoded = encodeBytes(source, 0, source.length, NO_OPTIONS); 721 | } catch (java.io.IOException ex) { 722 | assert false : ex.getMessage(); 723 | } // end catch 724 | assert encoded != null; 725 | return encoded; 726 | } // end encodeBytes 727 | 728 | 729 | 730 | /** 731 | * Encodes a byte array into Base64 notation. 732 | *

733 | * Example options:

 734 |      *   GZIP: gzip-compresses object before encoding it.
 735 |      *   DO_BREAK_LINES: break lines at 76 characters
 736 |      *     Note: Technically, this makes your encoding non-compliant.
 737 |      * 
738 | *

739 | * Example: encodeBytes( myData, Base64.GZIP ) or 740 | *

741 | * Example: encodeBytes( myData, Base64.GZIP | Base64.DO_BREAK_LINES ) 742 | * 743 | * 744 | *

As of v 2.3, if there is an error with the GZIP stream, 745 | * the method will throw an java.io.IOException. This is new to v2.3! 746 | * In earlier versions, it just returned a null value, but 747 | * in retrospect that's a pretty poor way to handle it.

748 | * 749 | * 750 | * @param source The data to convert 751 | * @param options Specified options 752 | * @return The Base64-encoded data as a String 753 | * @see Base64#GZIP 754 | * @see Base64#DO_BREAK_LINES 755 | * @throws java.io.IOException if there is an error 756 | * @throws NullPointerException if source array is null 757 | * @since 2.0 758 | */ 759 | public static String encodeBytes( byte[] source, int options ) throws java.io.IOException { 760 | return encodeBytes( source, 0, source.length, options ); 761 | } // end encodeBytes 762 | 763 | 764 | /** 765 | * Encodes a byte array into Base64 notation. 766 | * Does not GZip-compress data. 767 | * 768 | *

As of v 2.3, if there is an error, 769 | * the method will throw an java.io.IOException. This is new to v2.3! 770 | * In earlier versions, it just returned a null value, but 771 | * in retrospect that's a pretty poor way to handle it.

772 | * 773 | * 774 | * @param source The data to convert 775 | * @param off Offset in array where conversion should begin 776 | * @param len Length of data to convert 777 | * @return The Base64-encoded data as a String 778 | * @throws NullPointerException if source array is null 779 | * @throws IllegalArgumentException if source array, offset, or length are invalid 780 | * @since 1.4 781 | */ 782 | public static String encodeBytes( byte[] source, int off, int len ) { 783 | // Since we're not going to have the GZIP encoding turned on, 784 | // we're not going to have an java.io.IOException thrown, so 785 | // we should not force the user to have to catch it. 786 | String encoded = null; 787 | try { 788 | encoded = encodeBytes( source, off, len, NO_OPTIONS ); 789 | } catch (java.io.IOException ex) { 790 | assert false : ex.getMessage(); 791 | } // end catch 792 | assert encoded != null; 793 | return encoded; 794 | } // end encodeBytes 795 | 796 | 797 | 798 | /** 799 | * Encodes a byte array into Base64 notation. 800 | *

801 | * Example options:

 802 |      *   GZIP: gzip-compresses object before encoding it.
 803 |      *   DO_BREAK_LINES: break lines at 76 characters
 804 |      *     Note: Technically, this makes your encoding non-compliant.
 805 |      * 
806 | *

807 | * Example: encodeBytes( myData, Base64.GZIP ) or 808 | *

809 | * Example: encodeBytes( myData, Base64.GZIP | Base64.DO_BREAK_LINES ) 810 | * 811 | * 812 | *

As of v 2.3, if there is an error with the GZIP stream, 813 | * the method will throw an java.io.IOException. This is new to v2.3! 814 | * In earlier versions, it just returned a null value, but 815 | * in retrospect that's a pretty poor way to handle it.

816 | * 817 | * 818 | * @param source The data to convert 819 | * @param off Offset in array where conversion should begin 820 | * @param len Length of data to convert 821 | * @param options Specified options 822 | * @return The Base64-encoded data as a String 823 | * @see Base64#GZIP 824 | * @see Base64#DO_BREAK_LINES 825 | * @throws java.io.IOException if there is an error 826 | * @throws NullPointerException if source array is null 827 | * @throws IllegalArgumentException if source array, offset, or length are invalid 828 | * @since 2.0 829 | */ 830 | public static String encodeBytes( byte[] source, int off, int len, int options ) throws java.io.IOException { 831 | byte[] encoded = encodeBytesToBytes( source, off, len, options ); 832 | 833 | // Return value according to relevant encoding. 834 | try { 835 | return new String( encoded, PREFERRED_ENCODING ); 836 | } // end try 837 | catch (java.io.UnsupportedEncodingException uue) { 838 | return new String( encoded ); 839 | } // end catch 840 | 841 | } // end encodeBytes 842 | 843 | 844 | 845 | 846 | /** 847 | * Similar to {@link #encodeBytes(byte[])} but returns 848 | * a byte array instead of instantiating a String. This is more efficient 849 | * if you're working with I/O streams and have large data sets to encode. 850 | * 851 | * 852 | * @param source The data to convert 853 | * @return The Base64-encoded data as a byte[] (of ASCII characters) 854 | * @throws NullPointerException if source array is null 855 | * @since 2.3.1 856 | */ 857 | public static byte[] encodeBytesToBytes( byte[] source ) { 858 | byte[] encoded = null; 859 | try { 860 | encoded = encodeBytesToBytes( source, 0, source.length, Base64.NO_OPTIONS ); 861 | } catch( java.io.IOException ex ) { 862 | assert false : "IOExceptions only come from GZipping, which is turned off: " + ex.getMessage(); 863 | } 864 | return encoded; 865 | } 866 | 867 | 868 | /** 869 | * Similar to {@link #encodeBytes(byte[], int, int, int)} but returns 870 | * a byte array instead of instantiating a String. This is more efficient 871 | * if you're working with I/O streams and have large data sets to encode. 872 | * 873 | * 874 | * @param source The data to convert 875 | * @param off Offset in array where conversion should begin 876 | * @param len Length of data to convert 877 | * @param options Specified options 878 | * @return The Base64-encoded data as a String 879 | * @see Base64#GZIP 880 | * @see Base64#DO_BREAK_LINES 881 | * @throws java.io.IOException if there is an error 882 | * @throws NullPointerException if source array is null 883 | * @throws IllegalArgumentException if source array, offset, or length are invalid 884 | * @since 2.3.1 885 | */ 886 | public static byte[] encodeBytesToBytes( byte[] source, int off, int len, int options ) throws java.io.IOException { 887 | 888 | if( source == null ){ 889 | throw new NullPointerException( "Cannot serialize a null array." ); 890 | } // end if: null 891 | 892 | if( off < 0 ){ 893 | throw new IllegalArgumentException( "Cannot have negative offset: " + off ); 894 | } // end if: off < 0 895 | 896 | if( len < 0 ){ 897 | throw new IllegalArgumentException( "Cannot have length offset: " + len ); 898 | } // end if: len < 0 899 | 900 | if( off + len > source.length ){ 901 | throw new IllegalArgumentException( 902 | String.format( "Cannot have offset of %d and length of %d with array of length %d", off,len,source.length)); 903 | } // end if: off < 0 904 | 905 | 906 | 907 | // Compress? 908 | if( (options & GZIP) != 0 ) { 909 | java.io.ByteArrayOutputStream baos = null; 910 | java.util.zip.GZIPOutputStream gzos = null; 911 | Base64.OutputStream b64os = null; 912 | 913 | try { 914 | // GZip -> Base64 -> ByteArray 915 | baos = new java.io.ByteArrayOutputStream(); 916 | b64os = new Base64.OutputStream( baos, ENCODE | options ); 917 | gzos = new java.util.zip.GZIPOutputStream( b64os ); 918 | 919 | gzos.write( source, off, len ); 920 | gzos.close(); 921 | } // end try 922 | catch( java.io.IOException e ) { 923 | // Catch it and then throw it immediately so that 924 | // the finally{} block is called for cleanup. 925 | throw e; 926 | } // end catch 927 | finally { 928 | try{ gzos.close(); } catch( Exception e ){} 929 | try{ b64os.close(); } catch( Exception e ){} 930 | try{ baos.close(); } catch( Exception e ){} 931 | } // end finally 932 | 933 | return baos.toByteArray(); 934 | } // end if: compress 935 | 936 | // Else, don't compress. Better not to use streams at all then. 937 | else { 938 | boolean breakLines = (options & DO_BREAK_LINES) != 0; 939 | 940 | //int len43 = len * 4 / 3; 941 | //byte[] outBuff = new byte[ ( len43 ) // Main 4:3 942 | // + ( (len % 3) > 0 ? 4 : 0 ) // Account for padding 943 | // + (breakLines ? ( len43 / MAX_LINE_LENGTH ) : 0) ]; // New lines 944 | // Try to determine more precisely how big the array needs to be. 945 | // If we get it right, we don't have to do an array copy, and 946 | // we save a bunch of memory. 947 | int encLen = ( len / 3 ) * 4 + ( len % 3 > 0 ? 4 : 0 ); // Bytes needed for actual encoding 948 | if( breakLines ){ 949 | encLen += encLen / MAX_LINE_LENGTH; // Plus extra newline characters 950 | } 951 | byte[] outBuff = new byte[ encLen ]; 952 | 953 | 954 | int d = 0; 955 | int e = 0; 956 | int len2 = len - 2; 957 | int lineLength = 0; 958 | for( ; d < len2; d+=3, e+=4 ) { 959 | encode3to4( source, d+off, 3, outBuff, e, options ); 960 | 961 | lineLength += 4; 962 | if( breakLines && lineLength >= MAX_LINE_LENGTH ) 963 | { 964 | outBuff[e+4] = NEW_LINE; 965 | e++; 966 | lineLength = 0; 967 | } // end if: end of line 968 | } // en dfor: each piece of array 969 | 970 | if( d < len ) { 971 | encode3to4( source, d+off, len - d, outBuff, e, options ); 972 | e += 4; 973 | } // end if: some padding needed 974 | 975 | 976 | // Only resize array if we didn't guess it right. 977 | if( e <= outBuff.length - 1 ){ 978 | // If breaking lines and the last byte falls right at 979 | // the line length (76 bytes per line), there will be 980 | // one extra byte, and the array will need to be resized. 981 | // Not too bad of an estimate on array size, I'd say. 982 | byte[] finalOut = new byte[e]; 983 | System.arraycopy(outBuff,0, finalOut,0,e); 984 | //System.err.println("Having to resize array from " + outBuff.length + " to " + e ); 985 | return finalOut; 986 | } else { 987 | //System.err.println("No need to resize array."); 988 | return outBuff; 989 | } 990 | 991 | } // end else: don't compress 992 | 993 | } // end encodeBytesToBytes 994 | 995 | 996 | 997 | 998 | 999 | /* ******** D E C O D I N G M E T H O D S ******** */ 1000 | 1001 | 1002 | /** 1003 | * Decodes four bytes from array source 1004 | * and writes the resulting bytes (up to three of them) 1005 | * to destination. 1006 | * The source and destination arrays can be manipulated 1007 | * anywhere along their length by specifying 1008 | * srcOffset and destOffset. 1009 | * This method does not check to make sure your arrays 1010 | * are large enough to accomodate srcOffset + 4 for 1011 | * the source array or destOffset + 3 for 1012 | * the destination array. 1013 | * This method returns the actual number of bytes that 1014 | * were converted from the Base64 encoding. 1015 | *

This is the lowest level of the decoding methods with 1016 | * all possible parameters.

1017 | * 1018 | * 1019 | * @param source the array to convert 1020 | * @param srcOffset the index where conversion begins 1021 | * @param destination the array to hold the conversion 1022 | * @param destOffset the index where output will be put 1023 | * @param options alphabet type is pulled from this (standard, url-safe, ordered) 1024 | * @return the number of decoded bytes converted 1025 | * @throws NullPointerException if source or destination arrays are null 1026 | * @throws IllegalArgumentException if srcOffset or destOffset are invalid 1027 | * or there is not enough room in the array. 1028 | * @since 1.3 1029 | */ 1030 | private static int decode4to3( 1031 | byte[] source, int srcOffset, 1032 | byte[] destination, int destOffset, int options ) { 1033 | 1034 | // Lots of error checking and exception throwing 1035 | if( source == null ){ 1036 | throw new NullPointerException( "Source array was null." ); 1037 | } // end if 1038 | if( destination == null ){ 1039 | throw new NullPointerException( "Destination array was null." ); 1040 | } // end if 1041 | if( srcOffset < 0 || srcOffset + 3 >= source.length ){ 1042 | throw new IllegalArgumentException( String.format( 1043 | "Source array with length %d cannot have offset of %d and still process four bytes.", source.length, srcOffset ) ); 1044 | } // end if 1045 | if( destOffset < 0 || destOffset +2 >= destination.length ){ 1046 | throw new IllegalArgumentException( String.format( 1047 | "Destination array with length %d cannot have offset of %d and still store three bytes.", destination.length, destOffset ) ); 1048 | } // end if 1049 | 1050 | 1051 | byte[] DECODABET = getDecodabet( options ); 1052 | 1053 | // Example: Dk== 1054 | if( source[ srcOffset + 2] == EQUALS_SIGN ) { 1055 | // Two ways to do the same thing. Don't know which way I like best. 1056 | //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) 1057 | // | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 ); 1058 | int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) 1059 | | ( ( DECODABET[ source[ srcOffset + 1] ] & 0xFF ) << 12 ); 1060 | 1061 | destination[ destOffset ] = (byte)( outBuff >>> 16 ); 1062 | return 1; 1063 | } 1064 | 1065 | // Example: DkL= 1066 | else if( source[ srcOffset + 3 ] == EQUALS_SIGN ) { 1067 | // Two ways to do the same thing. Don't know which way I like best. 1068 | //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) 1069 | // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) 1070 | // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ); 1071 | int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) 1072 | | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 ) 1073 | | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6 ); 1074 | 1075 | destination[ destOffset ] = (byte)( outBuff >>> 16 ); 1076 | destination[ destOffset + 1 ] = (byte)( outBuff >>> 8 ); 1077 | return 2; 1078 | } 1079 | 1080 | // Example: DkLE 1081 | else { 1082 | // Two ways to do the same thing. Don't know which way I like best. 1083 | //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) 1084 | // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) 1085 | // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ) 1086 | // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 ); 1087 | int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) 1088 | | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 ) 1089 | | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6) 1090 | | ( ( DECODABET[ source[ srcOffset + 3 ] ] & 0xFF ) ); 1091 | 1092 | 1093 | destination[ destOffset ] = (byte)( outBuff >> 16 ); 1094 | destination[ destOffset + 1 ] = (byte)( outBuff >> 8 ); 1095 | destination[ destOffset + 2 ] = (byte)( outBuff ); 1096 | 1097 | return 3; 1098 | } 1099 | } // end decodeToBytes 1100 | 1101 | 1102 | 1103 | 1104 | 1105 | /** 1106 | * Low-level access to decoding ASCII characters in 1107 | * the form of a byte array. Ignores GUNZIP option, if 1108 | * it's set. This is not generally a recommended method, 1109 | * although it is used internally as part of the decoding process. 1110 | * Special case: if len = 0, an empty array is returned. Still, 1111 | * if you need more speed and reduced memory footprint (and aren't 1112 | * gzipping), consider this method. 1113 | * 1114 | * @param source The Base64 encoded data 1115 | * @return decoded data 1116 | * @since 2.3.1 1117 | */ 1118 | public static byte[] decode( byte[] source ) 1119 | throws java.io.IOException { 1120 | byte[] decoded = null; 1121 | // try { 1122 | decoded = decode( source, 0, source.length, Base64.NO_OPTIONS ); 1123 | // } catch( java.io.IOException ex ) { 1124 | // assert false : "IOExceptions only come from GZipping, which is turned off: " + ex.getMessage(); 1125 | // } 1126 | return decoded; 1127 | } 1128 | 1129 | 1130 | 1131 | /** 1132 | * Low-level access to decoding ASCII characters in 1133 | * the form of a byte array. Ignores GUNZIP option, if 1134 | * it's set. This is not generally a recommended method, 1135 | * although it is used internally as part of the decoding process. 1136 | * Special case: if len = 0, an empty array is returned. Still, 1137 | * if you need more speed and reduced memory footprint (and aren't 1138 | * gzipping), consider this method. 1139 | * 1140 | * @param source The Base64 encoded data 1141 | * @param off The offset of where to begin decoding 1142 | * @param len The length of characters to decode 1143 | * @param options Can specify options such as alphabet type to use 1144 | * @return decoded data 1145 | * @throws java.io.IOException If bogus characters exist in source data 1146 | * @since 1.3 1147 | */ 1148 | public static byte[] decode( byte[] source, int off, int len, int options ) 1149 | throws java.io.IOException { 1150 | 1151 | // Lots of error checking and exception throwing 1152 | if( source == null ){ 1153 | throw new NullPointerException( "Cannot decode null source array." ); 1154 | } // end if 1155 | if( off < 0 || off + len > source.length ){ 1156 | throw new IllegalArgumentException( String.format( 1157 | "Source array with length %d cannot have offset of %d and process %d bytes.", source.length, off, len ) ); 1158 | } // end if 1159 | 1160 | if( len == 0 ){ 1161 | return new byte[0]; 1162 | }else if( len < 4 ){ 1163 | throw new IllegalArgumentException( 1164 | "Base64-encoded string must have at least four characters, but length specified was " + len ); 1165 | } // end if 1166 | 1167 | byte[] DECODABET = getDecodabet( options ); 1168 | 1169 | int len34 = len * 3 / 4; // Estimate on array size 1170 | byte[] outBuff = new byte[ len34 ]; // Upper limit on size of output 1171 | int outBuffPosn = 0; // Keep track of where we're writing 1172 | 1173 | byte[] b4 = new byte[4]; // Four byte buffer from source, eliminating white space 1174 | int b4Posn = 0; // Keep track of four byte input buffer 1175 | int i = 0; // Source array counter 1176 | byte sbiDecode = 0; // Special value from DECODABET 1177 | 1178 | for( i = off; i < off+len; i++ ) { // Loop through source 1179 | 1180 | sbiDecode = DECODABET[ source[i]&0xFF ]; 1181 | 1182 | // White space, Equals sign, or legit Base64 character 1183 | // Note the values such as -5 and -9 in the 1184 | // DECODABETs at the top of the file. 1185 | if( sbiDecode >= WHITE_SPACE_ENC ) { 1186 | if( sbiDecode >= EQUALS_SIGN_ENC ) { 1187 | b4[ b4Posn++ ] = source[i]; // Save non-whitespace 1188 | if( b4Posn > 3 ) { // Time to decode? 1189 | outBuffPosn += decode4to3( b4, 0, outBuff, outBuffPosn, options ); 1190 | b4Posn = 0; 1191 | 1192 | // If that was the equals sign, break out of 'for' loop 1193 | if( source[i] == EQUALS_SIGN ) { 1194 | break; 1195 | } // end if: equals sign 1196 | } // end if: quartet built 1197 | } // end if: equals sign or better 1198 | } // end if: white space, equals sign or better 1199 | else { 1200 | // There's a bad input character in the Base64 stream. 1201 | throw new java.io.IOException( String.format( 1202 | "Bad Base64 input character decimal %d in array position %d", ((int)source[i])&0xFF, i ) ); 1203 | } // end else: 1204 | } // each input character 1205 | 1206 | byte[] out = new byte[ outBuffPosn ]; 1207 | System.arraycopy( outBuff, 0, out, 0, outBuffPosn ); 1208 | return out; 1209 | } // end decode 1210 | 1211 | 1212 | 1213 | 1214 | /** 1215 | * Decodes data from Base64 notation, automatically 1216 | * detecting gzip-compressed data and decompressing it. 1217 | * 1218 | * @param s the string to decode 1219 | * @return the decoded data 1220 | * @throws java.io.IOException If there is a problem 1221 | * @since 1.4 1222 | */ 1223 | public static byte[] decode( String s ) throws java.io.IOException { 1224 | return decode( s, NO_OPTIONS ); 1225 | } 1226 | 1227 | 1228 | 1229 | /** 1230 | * Decodes data from Base64 notation, automatically 1231 | * detecting gzip-compressed data and decompressing it. 1232 | * 1233 | * @param s the string to decode 1234 | * @param options encode options such as URL_SAFE 1235 | * @return the decoded data 1236 | * @throws java.io.IOException if there is an error 1237 | * @throws NullPointerException if s is null 1238 | * @since 1.4 1239 | */ 1240 | public static byte[] decode( String s, int options ) throws java.io.IOException { 1241 | 1242 | if( s == null ){ 1243 | throw new NullPointerException( "Input string was null." ); 1244 | } // end if 1245 | 1246 | byte[] bytes; 1247 | try { 1248 | bytes = s.getBytes( PREFERRED_ENCODING ); 1249 | } // end try 1250 | catch( java.io.UnsupportedEncodingException uee ) { 1251 | bytes = s.getBytes(); 1252 | } // end catch 1253 | // 1254 | 1255 | // Decode 1256 | bytes = decode( bytes, 0, bytes.length, options ); 1257 | 1258 | // Check to see if it's gzip-compressed 1259 | // GZIP Magic Two-Byte Number: 0x8b1f (35615) 1260 | boolean dontGunzip = (options & DONT_GUNZIP) != 0; 1261 | if( (bytes != null) && (bytes.length >= 4) && (!dontGunzip) ) { 1262 | 1263 | int head = ((int)bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00); 1264 | if( java.util.zip.GZIPInputStream.GZIP_MAGIC == head ) { 1265 | java.io.ByteArrayInputStream bais = null; 1266 | java.util.zip.GZIPInputStream gzis = null; 1267 | java.io.ByteArrayOutputStream baos = null; 1268 | byte[] buffer = new byte[2048]; 1269 | int length = 0; 1270 | 1271 | try { 1272 | baos = new java.io.ByteArrayOutputStream(); 1273 | bais = new java.io.ByteArrayInputStream( bytes ); 1274 | gzis = new java.util.zip.GZIPInputStream( bais ); 1275 | 1276 | while( ( length = gzis.read( buffer ) ) >= 0 ) { 1277 | baos.write(buffer,0,length); 1278 | } // end while: reading input 1279 | 1280 | // No error? Get new bytes. 1281 | bytes = baos.toByteArray(); 1282 | 1283 | } // end try 1284 | catch( java.io.IOException e ) { 1285 | e.printStackTrace(); 1286 | // Just return originally-decoded bytes 1287 | } // end catch 1288 | finally { 1289 | try{ baos.close(); } catch( Exception e ){} 1290 | try{ gzis.close(); } catch( Exception e ){} 1291 | try{ bais.close(); } catch( Exception e ){} 1292 | } // end finally 1293 | 1294 | } // end if: gzipped 1295 | } // end if: bytes.length >= 2 1296 | 1297 | return bytes; 1298 | } // end decode 1299 | 1300 | 1301 | 1302 | /** 1303 | * Attempts to decode Base64 data and deserialize a Java 1304 | * Object within. Returns null if there was an error. 1305 | * 1306 | * @param encodedObject The Base64 data to decode 1307 | * @return The decoded and deserialized object 1308 | * @throws NullPointerException if encodedObject is null 1309 | * @throws java.io.IOException if there is a general error 1310 | * @throws ClassNotFoundException if the decoded object is of a 1311 | * class that cannot be found by the JVM 1312 | * @since 1.5 1313 | */ 1314 | public static Object decodeToObject( String encodedObject ) 1315 | throws java.io.IOException, java.lang.ClassNotFoundException { 1316 | return decodeToObject(encodedObject,NO_OPTIONS,null); 1317 | } 1318 | 1319 | 1320 | /** 1321 | * Attempts to decode Base64 data and deserialize a Java 1322 | * Object within. Returns null if there was an error. 1323 | * If loader is not null, it will be the class loader 1324 | * used when deserializing. 1325 | * 1326 | * @param encodedObject The Base64 data to decode 1327 | * @param options Various parameters related to decoding 1328 | * @param loader Optional class loader to use in deserializing classes. 1329 | * @return The decoded and deserialized object 1330 | * @throws NullPointerException if encodedObject is null 1331 | * @throws java.io.IOException if there is a general error 1332 | * @throws ClassNotFoundException if the decoded object is of a 1333 | * class that cannot be found by the JVM 1334 | * @since 2.3.4 1335 | */ 1336 | public static Object decodeToObject( 1337 | String encodedObject, int options, final ClassLoader loader ) 1338 | throws java.io.IOException, java.lang.ClassNotFoundException { 1339 | 1340 | // Decode and gunzip if necessary 1341 | byte[] objBytes = decode( encodedObject, options ); 1342 | 1343 | java.io.ByteArrayInputStream bais = null; 1344 | java.io.ObjectInputStream ois = null; 1345 | Object obj = null; 1346 | 1347 | try { 1348 | bais = new java.io.ByteArrayInputStream( objBytes ); 1349 | 1350 | // If no custom class loader is provided, use Java's builtin OIS. 1351 | if( loader == null ){ 1352 | ois = new java.io.ObjectInputStream( bais ); 1353 | } // end if: no loader provided 1354 | 1355 | // Else make a customized object input stream that uses 1356 | // the provided class loader. 1357 | else { 1358 | ois = new java.io.ObjectInputStream(bais){ 1359 | @Override 1360 | public Class resolveClass(java.io.ObjectStreamClass streamClass) 1361 | throws java.io.IOException, ClassNotFoundException { 1362 | Class c = Class.forName(streamClass.getName(), false, loader); 1363 | if( c == null ){ 1364 | return super.resolveClass(streamClass); 1365 | } else { 1366 | return c; // Class loader knows of this class. 1367 | } // end else: not null 1368 | } // end resolveClass 1369 | }; // end ois 1370 | } // end else: no custom class loader 1371 | 1372 | obj = ois.readObject(); 1373 | } // end try 1374 | catch( java.io.IOException e ) { 1375 | throw e; // Catch and throw in order to execute finally{} 1376 | } // end catch 1377 | catch( java.lang.ClassNotFoundException e ) { 1378 | throw e; // Catch and throw in order to execute finally{} 1379 | } // end catch 1380 | finally { 1381 | try{ bais.close(); } catch( Exception e ){} 1382 | try{ ois.close(); } catch( Exception e ){} 1383 | } // end finally 1384 | 1385 | return obj; 1386 | } // end decodeObject 1387 | 1388 | 1389 | 1390 | /** 1391 | * Convenience method for encoding data to a file. 1392 | * 1393 | *

As of v 2.3, if there is a error, 1394 | * the method will throw an java.io.IOException. This is new to v2.3! 1395 | * In earlier versions, it just returned false, but 1396 | * in retrospect that's a pretty poor way to handle it.

1397 | * 1398 | * @param dataToEncode byte array of data to encode in base64 form 1399 | * @param filename Filename for saving encoded data 1400 | * @throws java.io.IOException if there is an error 1401 | * @throws NullPointerException if dataToEncode is null 1402 | * @since 2.1 1403 | */ 1404 | public static void encodeToFile( byte[] dataToEncode, String filename ) 1405 | throws java.io.IOException { 1406 | 1407 | if( dataToEncode == null ){ 1408 | throw new NullPointerException( "Data to encode was null." ); 1409 | } // end iff 1410 | 1411 | Base64.OutputStream bos = null; 1412 | try { 1413 | bos = new Base64.OutputStream( 1414 | new java.io.FileOutputStream( filename ), Base64.ENCODE ); 1415 | bos.write( dataToEncode ); 1416 | } // end try 1417 | catch( java.io.IOException e ) { 1418 | throw e; // Catch and throw to execute finally{} block 1419 | } // end catch: java.io.IOException 1420 | finally { 1421 | try{ bos.close(); } catch( Exception e ){} 1422 | } // end finally 1423 | 1424 | } // end encodeToFile 1425 | 1426 | 1427 | /** 1428 | * Convenience method for decoding data to a file. 1429 | * 1430 | *

As of v 2.3, if there is a error, 1431 | * the method will throw an java.io.IOException. This is new to v2.3! 1432 | * In earlier versions, it just returned false, but 1433 | * in retrospect that's a pretty poor way to handle it.

1434 | * 1435 | * @param dataToDecode Base64-encoded data as a string 1436 | * @param filename Filename for saving decoded data 1437 | * @throws java.io.IOException if there is an error 1438 | * @since 2.1 1439 | */ 1440 | public static void decodeToFile( String dataToDecode, String filename ) 1441 | throws java.io.IOException { 1442 | 1443 | Base64.OutputStream bos = null; 1444 | try{ 1445 | bos = new Base64.OutputStream( 1446 | new java.io.FileOutputStream( filename ), Base64.DECODE ); 1447 | bos.write( dataToDecode.getBytes( PREFERRED_ENCODING ) ); 1448 | } // end try 1449 | catch( java.io.IOException e ) { 1450 | throw e; // Catch and throw to execute finally{} block 1451 | } // end catch: java.io.IOException 1452 | finally { 1453 | try{ bos.close(); } catch( Exception e ){} 1454 | } // end finally 1455 | 1456 | } // end decodeToFile 1457 | 1458 | 1459 | 1460 | 1461 | /** 1462 | * Convenience method for reading a base64-encoded 1463 | * file and decoding it. 1464 | * 1465 | *

As of v 2.3, if there is a error, 1466 | * the method will throw an java.io.IOException. This is new to v2.3! 1467 | * In earlier versions, it just returned false, but 1468 | * in retrospect that's a pretty poor way to handle it.

1469 | * 1470 | * @param filename Filename for reading encoded data 1471 | * @return decoded byte array 1472 | * @throws java.io.IOException if there is an error 1473 | * @since 2.1 1474 | */ 1475 | public static byte[] decodeFromFile( String filename ) 1476 | throws java.io.IOException { 1477 | 1478 | byte[] decodedData = null; 1479 | Base64.InputStream bis = null; 1480 | try 1481 | { 1482 | // Set up some useful variables 1483 | java.io.File file = new java.io.File( filename ); 1484 | byte[] buffer = null; 1485 | int length = 0; 1486 | int numBytes = 0; 1487 | 1488 | // Check for size of file 1489 | if( file.length() > Integer.MAX_VALUE ) 1490 | { 1491 | throw new java.io.IOException( "File is too big for this convenience method (" + file.length() + " bytes)." ); 1492 | } // end if: file too big for int index 1493 | buffer = new byte[ (int)file.length() ]; 1494 | 1495 | // Open a stream 1496 | bis = new Base64.InputStream( 1497 | new java.io.BufferedInputStream( 1498 | new java.io.FileInputStream( file ) ), Base64.DECODE ); 1499 | 1500 | // Read until done 1501 | while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 ) { 1502 | length += numBytes; 1503 | } // end while 1504 | 1505 | // Save in a variable to return 1506 | decodedData = new byte[ length ]; 1507 | System.arraycopy( buffer, 0, decodedData, 0, length ); 1508 | 1509 | } // end try 1510 | catch( java.io.IOException e ) { 1511 | throw e; // Catch and release to execute finally{} 1512 | } // end catch: java.io.IOException 1513 | finally { 1514 | try{ bis.close(); } catch( Exception e) {} 1515 | } // end finally 1516 | 1517 | return decodedData; 1518 | } // end decodeFromFile 1519 | 1520 | 1521 | 1522 | /** 1523 | * Convenience method for reading a binary file 1524 | * and base64-encoding it. 1525 | * 1526 | *

As of v 2.3, if there is a error, 1527 | * the method will throw an java.io.IOException. This is new to v2.3! 1528 | * In earlier versions, it just returned false, but 1529 | * in retrospect that's a pretty poor way to handle it.

1530 | * 1531 | * @param filename Filename for reading binary data 1532 | * @return base64-encoded string 1533 | * @throws java.io.IOException if there is an error 1534 | * @since 2.1 1535 | */ 1536 | public static String encodeFromFile( String filename ) 1537 | throws java.io.IOException { 1538 | 1539 | String encodedData = null; 1540 | Base64.InputStream bis = null; 1541 | try 1542 | { 1543 | // Set up some useful variables 1544 | java.io.File file = new java.io.File( filename ); 1545 | byte[] buffer = new byte[ Math.max((int)(file.length() * 1.4+1),40) ]; // Need max() for math on small files (v2.2.1); Need +1 for a few corner cases (v2.3.5) 1546 | int length = 0; 1547 | int numBytes = 0; 1548 | 1549 | // Open a stream 1550 | bis = new Base64.InputStream( 1551 | new java.io.BufferedInputStream( 1552 | new java.io.FileInputStream( file ) ), Base64.ENCODE ); 1553 | 1554 | // Read until done 1555 | while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 ) { 1556 | length += numBytes; 1557 | } // end while 1558 | 1559 | // Save in a variable to return 1560 | encodedData = new String( buffer, 0, length, Base64.PREFERRED_ENCODING ); 1561 | 1562 | } // end try 1563 | catch( java.io.IOException e ) { 1564 | throw e; // Catch and release to execute finally{} 1565 | } // end catch: java.io.IOException 1566 | finally { 1567 | try{ bis.close(); } catch( Exception e) {} 1568 | } // end finally 1569 | 1570 | return encodedData; 1571 | } // end encodeFromFile 1572 | 1573 | /** 1574 | * Reads infile and encodes it to outfile. 1575 | * 1576 | * @param infile Input file 1577 | * @param outfile Output file 1578 | * @throws java.io.IOException if there is an error 1579 | * @since 2.2 1580 | */ 1581 | public static void encodeFileToFile( String infile, String outfile ) 1582 | throws java.io.IOException { 1583 | 1584 | String encoded = Base64.encodeFromFile( infile ); 1585 | java.io.OutputStream out = null; 1586 | try{ 1587 | out = new java.io.BufferedOutputStream( 1588 | new java.io.FileOutputStream( outfile ) ); 1589 | out.write( encoded.getBytes("US-ASCII") ); // Strict, 7-bit output. 1590 | } // end try 1591 | catch( java.io.IOException e ) { 1592 | throw e; // Catch and release to execute finally{} 1593 | } // end catch 1594 | finally { 1595 | try { out.close(); } 1596 | catch( Exception ex ){} 1597 | } // end finally 1598 | } // end encodeFileToFile 1599 | 1600 | 1601 | /** 1602 | * Reads infile and decodes it to outfile. 1603 | * 1604 | * @param infile Input file 1605 | * @param outfile Output file 1606 | * @throws java.io.IOException if there is an error 1607 | * @since 2.2 1608 | */ 1609 | public static void decodeFileToFile( String infile, String outfile ) 1610 | throws java.io.IOException { 1611 | 1612 | byte[] decoded = Base64.decodeFromFile( infile ); 1613 | java.io.OutputStream out = null; 1614 | try{ 1615 | out = new java.io.BufferedOutputStream( 1616 | new java.io.FileOutputStream( outfile ) ); 1617 | out.write( decoded ); 1618 | } // end try 1619 | catch( java.io.IOException e ) { 1620 | throw e; // Catch and release to execute finally{} 1621 | } // end catch 1622 | finally { 1623 | try { out.close(); } 1624 | catch( Exception ex ){} 1625 | } // end finally 1626 | } // end decodeFileToFile 1627 | 1628 | 1629 | /* ******** I N N E R C L A S S I N P U T S T R E A M ******** */ 1630 | 1631 | 1632 | 1633 | /** 1634 | * A {@link Base64.InputStream} will read data from another 1635 | * java.io.InputStream, given in the constructor, 1636 | * and encode/decode to/from Base64 notation on the fly. 1637 | * 1638 | * @see Base64 1639 | * @since 1.3 1640 | */ 1641 | public static class InputStream extends java.io.FilterInputStream { 1642 | 1643 | private boolean encode; // Encoding or decoding 1644 | private int position; // Current position in the buffer 1645 | private byte[] buffer; // Small buffer holding converted data 1646 | private int bufferLength; // Length of buffer (3 or 4) 1647 | private int numSigBytes; // Number of meaningful bytes in the buffer 1648 | private int lineLength; 1649 | private boolean breakLines; // Break lines at less than 80 characters 1650 | private int options; // Record options used to create the stream. 1651 | private byte[] decodabet; // Local copies to avoid extra method calls 1652 | 1653 | 1654 | /** 1655 | * Constructs a {@link Base64.InputStream} in DECODE mode. 1656 | * 1657 | * @param in the java.io.InputStream from which to read data. 1658 | * @since 1.3 1659 | */ 1660 | public InputStream( java.io.InputStream in ) { 1661 | this( in, DECODE ); 1662 | } // end constructor 1663 | 1664 | 1665 | /** 1666 | * Constructs a {@link Base64.InputStream} in 1667 | * either ENCODE or DECODE mode. 1668 | *

1669 | * Valid options:

1670 |          *   ENCODE or DECODE: Encode or Decode as data is read.
1671 |          *   DO_BREAK_LINES: break lines at 76 characters
1672 |          *     (only meaningful when encoding)
1673 |          * 
1674 | *

1675 | * Example: new Base64.InputStream( in, Base64.DECODE ) 1676 | * 1677 | * 1678 | * @param in the java.io.InputStream from which to read data. 1679 | * @param options Specified options 1680 | * @see Base64#ENCODE 1681 | * @see Base64#DECODE 1682 | * @see Base64#DO_BREAK_LINES 1683 | * @since 2.0 1684 | */ 1685 | public InputStream( java.io.InputStream in, int options ) { 1686 | 1687 | super( in ); 1688 | this.options = options; // Record for later 1689 | this.breakLines = (options & DO_BREAK_LINES) > 0; 1690 | this.encode = (options & ENCODE) > 0; 1691 | this.bufferLength = encode ? 4 : 3; 1692 | this.buffer = new byte[ bufferLength ]; 1693 | this.position = -1; 1694 | this.lineLength = 0; 1695 | this.decodabet = getDecodabet(options); 1696 | } // end constructor 1697 | 1698 | /** 1699 | * Reads enough of the input stream to convert 1700 | * to/from Base64 and returns the next byte. 1701 | * 1702 | * @return next byte 1703 | * @since 1.3 1704 | */ 1705 | @Override 1706 | public int read() throws java.io.IOException { 1707 | 1708 | // Do we need to get data? 1709 | if( position < 0 ) { 1710 | if( encode ) { 1711 | byte[] b3 = new byte[3]; 1712 | int numBinaryBytes = 0; 1713 | for( int i = 0; i < 3; i++ ) { 1714 | int b = in.read(); 1715 | 1716 | // If end of stream, b is -1. 1717 | if( b >= 0 ) { 1718 | b3[i] = (byte)b; 1719 | numBinaryBytes++; 1720 | } else { 1721 | break; // out of for loop 1722 | } // end else: end of stream 1723 | 1724 | } // end for: each needed input byte 1725 | 1726 | if( numBinaryBytes > 0 ) { 1727 | encode3to4( b3, 0, numBinaryBytes, buffer, 0, options ); 1728 | position = 0; 1729 | numSigBytes = 4; 1730 | } // end if: got data 1731 | else { 1732 | return -1; // Must be end of stream 1733 | } // end else 1734 | } // end if: encoding 1735 | 1736 | // Else decoding 1737 | else { 1738 | byte[] b4 = new byte[4]; 1739 | int i = 0; 1740 | for( i = 0; i < 4; i++ ) { 1741 | // Read four "meaningful" bytes: 1742 | int b = 0; 1743 | do{ b = in.read(); } 1744 | while( b >= 0 && decodabet[ b & 0x7f ] <= WHITE_SPACE_ENC ); 1745 | 1746 | if( b < 0 ) { 1747 | break; // Reads a -1 if end of stream 1748 | } // end if: end of stream 1749 | 1750 | b4[i] = (byte)b; 1751 | } // end for: each needed input byte 1752 | 1753 | if( i == 4 ) { 1754 | numSigBytes = decode4to3( b4, 0, buffer, 0, options ); 1755 | position = 0; 1756 | } // end if: got four characters 1757 | else if( i == 0 ){ 1758 | return -1; 1759 | } // end else if: also padded correctly 1760 | else { 1761 | // Must have broken out from above. 1762 | throw new java.io.IOException( "Improperly padded Base64 input." ); 1763 | } // end 1764 | 1765 | } // end else: decode 1766 | } // end else: get data 1767 | 1768 | // Got data? 1769 | if( position >= 0 ) { 1770 | // End of relevant data? 1771 | if( /*!encode &&*/ position >= numSigBytes ){ 1772 | return -1; 1773 | } // end if: got data 1774 | 1775 | if( encode && breakLines && lineLength >= MAX_LINE_LENGTH ) { 1776 | lineLength = 0; 1777 | return '\n'; 1778 | } // end if 1779 | else { 1780 | lineLength++; // This isn't important when decoding 1781 | // but throwing an extra "if" seems 1782 | // just as wasteful. 1783 | 1784 | int b = buffer[ position++ ]; 1785 | 1786 | if( position >= bufferLength ) { 1787 | position = -1; 1788 | } // end if: end 1789 | 1790 | return b & 0xFF; // This is how you "cast" a byte that's 1791 | // intended to be unsigned. 1792 | } // end else 1793 | } // end if: position >= 0 1794 | 1795 | // Else error 1796 | else { 1797 | throw new java.io.IOException( "Error in Base64 code reading stream." ); 1798 | } // end else 1799 | } // end read 1800 | 1801 | 1802 | /** 1803 | * Calls {@link #read()} repeatedly until the end of stream 1804 | * is reached or len bytes are read. 1805 | * Returns number of bytes read into array or -1 if 1806 | * end of stream is encountered. 1807 | * 1808 | * @param dest array to hold values 1809 | * @param off offset for array 1810 | * @param len max number of bytes to read into array 1811 | * @return bytes read into array or -1 if end of stream is encountered. 1812 | * @since 1.3 1813 | */ 1814 | @Override 1815 | public int read( byte[] dest, int off, int len ) 1816 | throws java.io.IOException { 1817 | int i; 1818 | int b; 1819 | for( i = 0; i < len; i++ ) { 1820 | b = read(); 1821 | 1822 | if( b >= 0 ) { 1823 | dest[off + i] = (byte) b; 1824 | } 1825 | else if( i == 0 ) { 1826 | return -1; 1827 | } 1828 | else { 1829 | break; // Out of 'for' loop 1830 | } // Out of 'for' loop 1831 | } // end for: each byte read 1832 | return i; 1833 | } // end read 1834 | 1835 | } // end inner class InputStream 1836 | 1837 | 1838 | 1839 | 1840 | 1841 | 1842 | /* ******** I N N E R C L A S S O U T P U T S T R E A M ******** */ 1843 | 1844 | 1845 | 1846 | /** 1847 | * A {@link Base64.OutputStream} will write data to another 1848 | * java.io.OutputStream, given in the constructor, 1849 | * and encode/decode to/from Base64 notation on the fly. 1850 | * 1851 | * @see Base64 1852 | * @since 1.3 1853 | */ 1854 | public static class OutputStream extends java.io.FilterOutputStream { 1855 | 1856 | private boolean encode; 1857 | private int position; 1858 | private byte[] buffer; 1859 | private int bufferLength; 1860 | private int lineLength; 1861 | private boolean breakLines; 1862 | private byte[] b4; // Scratch used in a few places 1863 | private boolean suspendEncoding; 1864 | private int options; // Record for later 1865 | private byte[] decodabet; // Local copies to avoid extra method calls 1866 | 1867 | /** 1868 | * Constructs a {@link Base64.OutputStream} in ENCODE mode. 1869 | * 1870 | * @param out the java.io.OutputStream to which data will be written. 1871 | * @since 1.3 1872 | */ 1873 | public OutputStream( java.io.OutputStream out ) { 1874 | this( out, ENCODE ); 1875 | } // end constructor 1876 | 1877 | 1878 | /** 1879 | * Constructs a {@link Base64.OutputStream} in 1880 | * either ENCODE or DECODE mode. 1881 | *

1882 | * Valid options:

1883 |          *   ENCODE or DECODE: Encode or Decode as data is read.
1884 |          *   DO_BREAK_LINES: don't break lines at 76 characters
1885 |          *     (only meaningful when encoding)
1886 |          * 
1887 | *

1888 | * Example: new Base64.OutputStream( out, Base64.ENCODE ) 1889 | * 1890 | * @param out the java.io.OutputStream to which data will be written. 1891 | * @param options Specified options. 1892 | * @see Base64#ENCODE 1893 | * @see Base64#DECODE 1894 | * @see Base64#DO_BREAK_LINES 1895 | * @since 1.3 1896 | */ 1897 | public OutputStream( java.io.OutputStream out, int options ) { 1898 | super( out ); 1899 | this.breakLines = (options & DO_BREAK_LINES) != 0; 1900 | this.encode = (options & ENCODE) != 0; 1901 | this.bufferLength = encode ? 3 : 4; 1902 | this.buffer = new byte[ bufferLength ]; 1903 | this.position = 0; 1904 | this.lineLength = 0; 1905 | this.suspendEncoding = false; 1906 | this.b4 = new byte[4]; 1907 | this.options = options; 1908 | this.decodabet = getDecodabet(options); 1909 | } // end constructor 1910 | 1911 | 1912 | /** 1913 | * Writes the byte to the output stream after 1914 | * converting to/from Base64 notation. 1915 | * When encoding, bytes are buffered three 1916 | * at a time before the output stream actually 1917 | * gets a write() call. 1918 | * When decoding, bytes are buffered four 1919 | * at a time. 1920 | * 1921 | * @param theByte the byte to write 1922 | * @since 1.3 1923 | */ 1924 | @Override 1925 | public void write(int theByte) 1926 | throws java.io.IOException { 1927 | // Encoding suspended? 1928 | if( suspendEncoding ) { 1929 | this.out.write( theByte ); 1930 | return; 1931 | } // end if: supsended 1932 | 1933 | // Encode? 1934 | if( encode ) { 1935 | buffer[ position++ ] = (byte)theByte; 1936 | if( position >= bufferLength ) { // Enough to encode. 1937 | 1938 | this.out.write( encode3to4( b4, buffer, bufferLength, options ) ); 1939 | 1940 | lineLength += 4; 1941 | if( breakLines && lineLength >= MAX_LINE_LENGTH ) { 1942 | this.out.write( NEW_LINE ); 1943 | lineLength = 0; 1944 | } // end if: end of line 1945 | 1946 | position = 0; 1947 | } // end if: enough to output 1948 | } // end if: encoding 1949 | 1950 | // Else, Decoding 1951 | else { 1952 | // Meaningful Base64 character? 1953 | if( decodabet[ theByte & 0x7f ] > WHITE_SPACE_ENC ) { 1954 | buffer[ position++ ] = (byte)theByte; 1955 | if( position >= bufferLength ) { // Enough to output. 1956 | 1957 | int len = Base64.decode4to3( buffer, 0, b4, 0, options ); 1958 | out.write( b4, 0, len ); 1959 | position = 0; 1960 | } // end if: enough to output 1961 | } // end if: meaningful base64 character 1962 | else if( decodabet[ theByte & 0x7f ] != WHITE_SPACE_ENC ) { 1963 | throw new java.io.IOException( "Invalid character in Base64 data." ); 1964 | } // end else: not white space either 1965 | } // end else: decoding 1966 | } // end write 1967 | 1968 | 1969 | 1970 | /** 1971 | * Calls {@link #write(int)} repeatedly until len 1972 | * bytes are written. 1973 | * 1974 | * @param theBytes array from which to read bytes 1975 | * @param off offset for array 1976 | * @param len max number of bytes to read into array 1977 | * @since 1.3 1978 | */ 1979 | @Override 1980 | public void write( byte[] theBytes, int off, int len ) 1981 | throws java.io.IOException { 1982 | // Encoding suspended? 1983 | if( suspendEncoding ) { 1984 | this.out.write( theBytes, off, len ); 1985 | return; 1986 | } // end if: supsended 1987 | 1988 | for( int i = 0; i < len; i++ ) { 1989 | write( theBytes[ off + i ] ); 1990 | } // end for: each byte written 1991 | 1992 | } // end write 1993 | 1994 | 1995 | 1996 | /** 1997 | * Method added by PHIL. [Thanks, PHIL. -Rob] 1998 | * This pads the buffer without closing the stream. 1999 | * @throws java.io.IOException if there's an error. 2000 | */ 2001 | public void flushBase64() throws java.io.IOException { 2002 | if( position > 0 ) { 2003 | if( encode ) { 2004 | out.write( encode3to4( b4, buffer, position, options ) ); 2005 | position = 0; 2006 | } // end if: encoding 2007 | else { 2008 | throw new java.io.IOException( "Base64 input not properly padded." ); 2009 | } // end else: decoding 2010 | } // end if: buffer partially full 2011 | 2012 | } // end flush 2013 | 2014 | 2015 | /** 2016 | * Flushes and closes (I think, in the superclass) the stream. 2017 | * 2018 | * @since 1.3 2019 | */ 2020 | @Override 2021 | public void close() throws java.io.IOException { 2022 | // 1. Ensure that pending characters are written 2023 | flushBase64(); 2024 | 2025 | // 2. Actually close the stream 2026 | // Base class both flushes and closes. 2027 | super.close(); 2028 | 2029 | buffer = null; 2030 | out = null; 2031 | } // end close 2032 | 2033 | 2034 | 2035 | /** 2036 | * Suspends encoding of the stream. 2037 | * May be helpful if you need to embed a piece of 2038 | * base64-encoded data in a stream. 2039 | * 2040 | * @throws java.io.IOException if there's an error flushing 2041 | * @since 1.5.1 2042 | */ 2043 | public void suspendEncoding() throws java.io.IOException { 2044 | flushBase64(); 2045 | this.suspendEncoding = true; 2046 | } // end suspendEncoding 2047 | 2048 | 2049 | /** 2050 | * Resumes encoding of the stream. 2051 | * May be helpful if you need to embed a piece of 2052 | * base64-encoded data in a stream. 2053 | * 2054 | * @since 1.5.1 2055 | */ 2056 | public void resumeEncoding() { 2057 | this.suspendEncoding = false; 2058 | } // end resumeEncoding 2059 | 2060 | 2061 | 2062 | } // end inner class OutputStream 2063 | 2064 | 2065 | } // end class Base64 2066 | -------------------------------------------------------------------------------- /src/main/java/com/opera/link/apilib/LinkClient.java: -------------------------------------------------------------------------------- 1 | package com.opera.link.apilib; 2 | 3 | import java.io.IOException; 4 | import java.net.URISyntaxException; 5 | import java.util.ArrayList; 6 | import java.util.Collection; 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | import net.oauth.OAuthAccessor; 11 | import net.oauth.OAuthConsumer; 12 | import net.oauth.OAuthException; 13 | import net.oauth.OAuthMessage; 14 | import net.oauth.OAuthProblemException; 15 | import net.oauth.OAuthServiceProvider; 16 | import net.oauth.client.OAuthClient; 17 | import net.oauth.client.httpclient3.HttpClient3; 18 | 19 | import org.json.JSONArray; 20 | import org.json.JSONException; 21 | 22 | import com.opera.link.apilib.exceptions.LibOperaLinkException; 23 | import com.opera.link.apilib.exceptions.LinkAccessDeniedException; 24 | import com.opera.link.apilib.exceptions.LinkItemNotFound; 25 | import com.opera.link.apilib.exceptions.LinkResponseFormatException; 26 | import com.opera.link.apilib.items.BookmarkFolderEntry; 27 | import com.opera.link.apilib.items.Element; 28 | import com.opera.link.apilib.items.FolderEntry; 29 | import com.opera.link.apilib.items.FolderInterface; 30 | import com.opera.link.apilib.items.NoteFolderEntry; 31 | import com.opera.link.apilib.items.SpeedDial; 32 | import com.opera.link.apilib.items.UrlFilter; 33 | import com.opera.link.apilib.items.SearchEngine; 34 | 35 | import net.oauth.OAuth; 36 | 37 | /** 38 | * 39 | * The main class handling connection with Opera Link server. It supports user 40 | * authorisation and methods for getting and manipulating Opera Link elements. 41 | * 42 | *

43 | * Before some data with bookmarks, notes or speeddials can be exchanged with 44 | * server, the application must be granted access. To do so OAuth 1.0 protocol 45 | * based authorization method is performed. The application must be registered 46 | * at http://auth.opera.com/service/oauth/applications/ and have its consumerKey 47 | * and consumerSecret 48 | *

49 | * 50 | * @author pjarzebowski 51 | */ 52 | public class LinkClient { 53 | 54 | private String consumerKey; 55 | private String consumerSecret; 56 | 57 | private OAuthAccessor accessor; 58 | private OAuthServiceProvider provider; 59 | private OAuthClient client; 60 | private boolean isAuthorized = false; 61 | 62 | private static final String URL_SEPARATOR = "/"; 63 | 64 | public static final String OAUTH_URL = "https://auth.opera.com/service/oauth/"; 65 | public static final String URL_PREFIX = "https://link.api.opera.com/rest/"; 66 | public static final String OOB_CALLBACK = "oob"; 67 | public static final String OAUTH_CALLBACK = "oauth_callback"; 68 | public static final String OAUTH_VERIFIER = "oauth_verifier"; 69 | 70 | /** 71 | * Creates object for making requests to the Opera Link server. Connection 72 | * is not authorised after the object creation, the user must grant access 73 | * to his data on the authorisation website - to generat the url use {@code 74 | * getAuthorizationURL} method. 75 | * 76 | * @param consumerKey 77 | * Application key 78 | * @param consumerSecret 79 | * Application secret key 80 | */ 81 | public LinkClient(String consumerKey, String consumerSecret) { 82 | this.consumerKey = consumerKey; 83 | this.consumerSecret = consumerSecret; 84 | 85 | setLinkOAuthConnnection(); 86 | } 87 | 88 | private LinkClient(String consumerKey, String consumerSercret, 89 | String requestToken, String accessToken, String tokenSecret) { 90 | this(consumerKey, consumerSercret); 91 | accessor.accessToken = accessToken; 92 | accessor.requestToken = requestToken; 93 | accessor.tokenSecret = tokenSecret; 94 | } 95 | 96 | /** 97 | * Creates object for making requests to the Opera Link server. Connection 98 | * is not authorised, but OAuth request tokens where generated before. 99 | * 100 | * @param consumerKey 101 | * Application key 102 | * @param consumerSecret 103 | * Application secret key 104 | * @param requestToken 105 | * connection request token 106 | * @param tokenSecret 107 | * connection secret token 108 | */ 109 | public static LinkClient createFromRequestToken(String consumerKey, 110 | String consumerSercret, String requestToken, String tokenSecret) { 111 | return new LinkClient(consumerKey, consumerSercret, requestToken, null, 112 | tokenSecret); 113 | } 114 | 115 | /** 116 | * Creates object for making requests to the Opera Link server. Connection 117 | * is authorised. 118 | * 119 | * @param consumerKey 120 | * Application key 121 | * @param consumerSecret 122 | * Application secret key 123 | * @param accessToken 124 | * generated access token for connection 125 | * @param tokenSecret 126 | * connection secret token 127 | * @return 128 | */ 129 | public static LinkClient createFromAccessToken(String consumerKey, 130 | String consumerSecret, String accessToken, String tokenSecret) { 131 | return new LinkClient(consumerKey, consumerSecret, null, accessToken, 132 | tokenSecret); 133 | } 134 | 135 | private void setLinkOAuthConnnection() { 136 | provider = new OAuthServiceProvider(OAUTH_URL + "request_token", 137 | OAUTH_URL + "authorize", OAUTH_URL + "access_token"); 138 | OAuthConsumer consumer = new OAuthConsumer(OOB_CALLBACK, consumerKey, 139 | consumerSecret, provider); 140 | accessor = new OAuthAccessor(consumer); 141 | client = new OAuthClient(new HttpClient3()); 142 | } 143 | 144 | /** 145 | * Generates and returns new URL address where an user can grant access to 146 | * the application 147 | * 148 | * @param callback 149 | * URL address where user should be redirected after granting 150 | * access. If no redirection needed pass {@code 151 | * LinkClient.OOB_CALLBACK} value 152 | * @return URL address of authorisation website 153 | */ 154 | public String getAuthorizationURL(String callback) 155 | throws LibOperaLinkException { 156 | try { 157 | HashMap requestParams = new HashMap(); 158 | requestParams.put(OAUTH_CALLBACK, OOB_CALLBACK); 159 | client.getRequestToken(accessor, OAuthMessage.POST, requestParams 160 | .entrySet()); 161 | } catch (OAuthException e) { 162 | throw new LinkAccessDeniedException(e); 163 | } catch (Exception e) { 164 | throw new LibOperaLinkException(e); 165 | } 166 | return accessor.consumer.serviceProvider.userAuthorizationURL 167 | + "?oauth_token=" + accessor.requestToken + "&oauth_callback=" 168 | + callback; 169 | } 170 | 171 | /** 172 | * Performs the last step of OAauth authorisation - generates an access 173 | * token. 174 | * 175 | * @throws LibOperaLinkException 176 | */ 177 | public void grantAccess(String verifier) throws LibOperaLinkException { 178 | try { 179 | HashMap requestParams = new HashMap(); 180 | requestParams.put(OAUTH_VERIFIER, verifier); 181 | client.getAccessToken(accessor, OAuthMessage.POST, requestParams 182 | .entrySet()); 183 | /* client.getAccessToken(accessor, OAuthMessage.POST, 184 | OAuth.newList(OAUTH_VERIFIER, verifier.toString())); */ 185 | isAuthorized = true; 186 | } catch (OAuthException e) { 187 | throw new LinkAccessDeniedException(e); 188 | } catch (Exception e) { 189 | throw new LibOperaLinkException(e); 190 | } 191 | } 192 | 193 | protected JSONArray makeRequest(String requestUrl, 194 | String requestMethod, HashMap params) 195 | throws LinkItemNotFound, LinkAccessDeniedException, 196 | LinkResponseFormatException, LibOperaLinkException { 197 | // get server response 198 | String response = null; 199 | OAuthMessage responseMessage = null; 200 | try { 201 | responseMessage = client.invoke(accessor, requestMethod, 202 | requestUrl, addRequestParameters(params)); 203 | } catch (IOException e1) { 204 | e1.printStackTrace(); 205 | throw new LibOperaLinkException("connection to server refused"); 206 | } catch (OAuthException e1) { 207 | OAuthProblemException problemExep = (OAuthProblemException) e1; 208 | LibOperaLinkException.throwCommunicationExeption(problemExep 209 | .getHttpStatusCode(), e1); 210 | } catch (URISyntaxException e1) { 211 | throw new LibOperaLinkException(e1); 212 | } 213 | try { 214 | response = responseMessage.readBodyAsString(); 215 | } catch (IOException e1) { 216 | throw new LinkResponseFormatException(e1); 217 | } 218 | 219 | // load server response to json list 220 | if (response == null || response.length() == 0) { 221 | return null; 222 | } 223 | try { 224 | JSONArray items = new JSONArray(response); 225 | return items; 226 | } catch (JSONException e) { 227 | e.printStackTrace(); 228 | throw new LinkResponseFormatException(e); 229 | } 230 | } 231 | 232 | protected Collection> addRequestParameters( 233 | HashMap params) { 234 | if (params == null) { 235 | params = new HashMap(); 236 | } 237 | params.put(ApiParameters.API_OUTPUT_PARAM, 238 | ApiParameters.JSON_OUTPUT_PARAM); 239 | return params.entrySet(); 240 | } 241 | 242 | protected ArrayList requestItems(Class cls, 243 | String datatype, boolean recursive, String rootID) 244 | throws LinkItemNotFound, LinkAccessDeniedException, 245 | LinkResponseFormatException, LibOperaLinkException { 246 | // create request URL 247 | String itemsDepth = ApiParameters.URL_GET_CHILDREN_PARAM; 248 | if (recursive) { 249 | itemsDepth = ApiParameters.URL_GET_DESCENDANTS_PARAM; 250 | } 251 | 252 | String requestUrl = createUrl(datatype, rootID) + itemsDepth; 253 | 254 | JSONArray items = makeRequest(requestUrl, HttpClient3.GET, null); 255 | try { 256 | return Element.elementsFactory(items, cls); 257 | } catch (JSONException e) { 258 | e.printStackTrace(); 259 | throw new LinkResponseFormatException(e); 260 | } 261 | } 262 | 263 | protected T requestItem(Class cls, String datatype, 264 | String rootID) throws LinkItemNotFound, LinkAccessDeniedException, 265 | LinkResponseFormatException, LibOperaLinkException { 266 | // create request URL 267 | String requestUrl = createUrl(datatype, rootID); 268 | 269 | JSONArray items = makeRequest(requestUrl, HttpClient3.GET, null); 270 | try { 271 | return Element.elementsFactory(items, cls).get(0); 272 | } catch (JSONException e) { 273 | e.printStackTrace(); 274 | throw new LibOperaLinkException(e); 275 | } 276 | } 277 | 278 | protected String createUrl(String datatype, String rootID) { 279 | StringBuilder sb = new StringBuilder(URL_PREFIX); 280 | sb.append(datatype); 281 | sb.append(URL_SEPARATOR); 282 | if (rootID != null) { 283 | sb.append(rootID); 284 | sb.append(URL_SEPARATOR); 285 | } 286 | return sb.toString(); 287 | } 288 | 289 | /** 290 | * Get public request token which is used during OAuth authorization process 291 | * 292 | * @return request token 293 | */ 294 | public String getRequestToken() { 295 | return accessor.requestToken; 296 | } 297 | 298 | /** 299 | * Get Public authorized token for OAuth communication 300 | */ 301 | public String getAccessToken() { 302 | return accessor.accessToken; 303 | } 304 | 305 | /** 306 | * Secret token for OAuth communication 307 | */ 308 | public String getTokenSecret() { 309 | return accessor.tokenSecret; 310 | } 311 | 312 | /** 313 | * True if authorization process is over and requests can be made to receive 314 | * data from server 315 | */ 316 | public boolean isAuthorized() { 317 | return isAuthorized; 318 | } 319 | 320 | /** 321 | * Fetches located in the OperaLink server's root folder bookmarks, bookmark 322 | * folders and bookmark separators. 323 | * 324 | * @param recursive 325 | * if true then all bookmarks are returned, otherwise only the 326 | * content of a root folder 327 | * @return elements of bookmark datatype 328 | * @throws LibOperaLinkException 329 | * thrown when the server communication exception occurs, see 330 | * its inner exception for details 331 | */ 332 | public ArrayList getRootBookmarks(boolean recursive) 333 | throws LinkItemNotFound, LinkAccessDeniedException, 334 | LinkResponseFormatException, LibOperaLinkException { 335 | return requestItems(BookmarkFolderEntry.class, 336 | BookmarkFolderEntry.DATATYPE, recursive, null); 337 | } 338 | 339 | /** 340 | * Fetches from the OperaLink server and returns the content of folder 341 | * (bookmarks, bookmark folders and bookmark separators) 342 | * 343 | * @param folder 344 | * folder which content is returned 345 | * @param recursive 346 | * if true then children are returned recursively, otherwise only 347 | * the direct content of a folder 348 | * @return elements of bookmark datatype 349 | * @throws LibOperaLinkException 350 | * thrown when the server communication exception occurs, see 351 | * its inner exception for details 352 | */ 353 | public ArrayList getBookmarksFromFolder( 354 | String folderId, boolean recursive) throws LinkItemNotFound, 355 | LinkAccessDeniedException, LinkResponseFormatException, 356 | LibOperaLinkException { 357 | return requestItems(BookmarkFolderEntry.class, 358 | BookmarkFolderEntry.DATATYPE, recursive, folderId); 359 | 360 | } 361 | 362 | /** 363 | * Fetches located in the OperaLink server's root folder notes, note folders 364 | * and note separators. 365 | * 366 | * @param folderId 367 | * ID of folder which content should be returned 368 | * @param recursive 369 | * if true then all bookmarks are returned, otherwise only the 370 | * content of a root folder 371 | * @return elements of bookmark datatype 372 | * @throws LibOperaLinkException 373 | */ 374 | public ArrayList getRootNotes(boolean recursive) 375 | throws LinkItemNotFound, LinkAccessDeniedException, 376 | LinkResponseFormatException, LibOperaLinkException { 377 | return requestItems(NoteFolderEntry.class, NoteFolderEntry.DATATYPE, 378 | recursive, null); 379 | } 380 | 381 | /** 382 | * Fetches from the OperaLink server and returns the content of folder 383 | * (notes, note folders and note separators) 384 | * 385 | * @param folderId 386 | * ID of folder which content should be returned 387 | * @param recursive 388 | * if true then all bookmarks are returned, otherwise only the 389 | * content of a root folder 390 | * @return elements of bookmark datatype 391 | * @throws LibOperaLinkException 392 | */ 393 | public ArrayList getNotesFromFolder(String folderId, 394 | boolean recursive) throws LinkItemNotFound, 395 | LinkAccessDeniedException, LinkResponseFormatException, 396 | LibOperaLinkException { 397 | return requestItems(NoteFolderEntry.class, NoteFolderEntry.DATATYPE, 398 | recursive, folderId); 399 | } 400 | 401 | /** 402 | * Fetches from the OperaLink server and returns all of the Speed Dials 403 | * 404 | * @return Speed Dials list 405 | * @throws LibOperaLinkException 406 | */ 407 | public ArrayList getSpeedDials() throws LinkItemNotFound, 408 | LinkAccessDeniedException, LinkResponseFormatException, 409 | LibOperaLinkException { 410 | return requestItems(SpeedDial.class, SpeedDial.ITEM_TYPE, false, null); 411 | } 412 | 413 | /** 414 | * Fetches from the OperaLink server and returns all of the UriFilters 415 | * 416 | * @return UrlFilters list 417 | * @throws LibOperaLinkException 418 | */ 419 | public ArrayList getUrlFilters() throws LinkItemNotFound, 420 | LinkAccessDeniedException, LinkResponseFormatException, 421 | LibOperaLinkException { 422 | return requestItems(UrlFilter.class, UrlFilter.ITEM_TYPE, false, null); 423 | } 424 | 425 | /** 426 | * Fetches from the OperaLink server and returns all of the Search Engines 427 | * 428 | * @return UriFilters list 429 | * @throws LibOperaLinkException 430 | */ 431 | public ArrayList getSearchEngines() throws LinkItemNotFound, 432 | LinkAccessDeniedException, LinkResponseFormatException, 433 | LibOperaLinkException { 434 | return requestItems(SearchEngine.class, SearchEngine.ITEM_TYPE, false, null); 435 | } 436 | 437 | /** 438 | * Gets from the OperaLink server and returns element of datatype which has 439 | * specified id 440 | * 441 | * @param id 442 | * @param datatype 443 | * can be one of BookmarkFolderEntry.getDatatype(), 444 | * NoteFolderEntry.getDatatype() or 445 | * SpeedDialFolderEntry.getDatatype() 446 | * @return Element got from the server 447 | * @throws LibOperaLinkException 448 | * thrown when the server communication exception occurs, see 449 | * its inner exception for details 450 | */ 451 | public Element getElement(String id, String datatype) 452 | throws LinkItemNotFound, LinkAccessDeniedException, 453 | LinkResponseFormatException, LibOperaLinkException { 454 | return requestItem(Element.class, datatype, id); 455 | } 456 | 457 | /** 458 | * Adds the element as a child of element with id=destination, it's appended 459 | * at the end of destination's children list If destination is null then 460 | * it's added to root folder (not valid for SpeedDial) 461 | * 462 | * @param element 463 | * An element to be added 464 | * @param destination 465 | * ID of an element which is going to be a parent of the added 466 | * element 467 | * @throws LibOperaLinkException 468 | * thrown when the server communication exception occurs 469 | */ 470 | public void add(Element element, String destination) 471 | throws LinkItemNotFound, LinkAccessDeniedException, 472 | LinkResponseFormatException, LibOperaLinkException { 473 | 474 | HashMap params = element.createParamsDict(); 475 | params.put(ApiParameters.API_METHOD_PARAM, ApiParameters.CREATE); 476 | params.put(Element.ITEM_TYPE_JSON_KEY, element.getItemType()); 477 | 478 | JSONArray arrayResponse = makeRequest(createUrl(element.getDatatype(), 479 | destination), HttpClient3.POST, params); 480 | try { 481 | element.updateParameters(arrayResponse.getJSONObject(0)); 482 | } catch (JSONException e) { 483 | e.printStackTrace(); 484 | throw new LibOperaLinkException(e); 485 | } 486 | } 487 | 488 | /** 489 | * Adds the element to the root folder 490 | * 491 | * @param element 492 | * An element to be added 493 | * @throws LibOperaLinkException 494 | * thrown when the server communication exception occurs 495 | */ 496 | public void add(FolderEntry element) throws LinkItemNotFound, 497 | LinkAccessDeniedException, LinkResponseFormatException, 498 | LibOperaLinkException { 499 | add(element, null); 500 | } 501 | 502 | /** 503 | * Adds the element to the folder 504 | * 505 | * @param 506 | * 507 | * @param element 508 | * An element to be added 509 | * @param folder 510 | * The destination folder of the element 511 | * @throws LibOperaLinkException 512 | * thrown when the server communication exception occurs or 513 | * folder parameter is not a folder element 514 | */ 515 | public , U extends FolderInterface> void addToFolder( 516 | FolderEntry element, U folderElement) throws LinkItemNotFound, 517 | LinkAccessDeniedException, LinkResponseFormatException, 518 | LibOperaLinkException { 519 | 520 | if (folderElement == null || folderElement.getId() == null) { 521 | throw new LinkItemNotFound(); 522 | } 523 | 524 | add(element, folderElement.getId()); 525 | 526 | } 527 | 528 | /** 529 | * Adds the SpeedDial 530 | * 531 | * @param speeddial 532 | * A SpeedDial to be added 533 | * @throws LibOperaLinkException 534 | * thrown when the server communication exception occurs or 535 | * folder parameter is not a folder element 536 | */ 537 | public void add(SpeedDial speeddial) throws LinkItemNotFound, 538 | LinkAccessDeniedException, LinkResponseFormatException, 539 | LibOperaLinkException { 540 | add(speeddial, String.valueOf(speeddial.getPosition())); 541 | } 542 | 543 | /** 544 | * Sends all of changes to the element and updates it in case of any 545 | * modifications made by other clients 546 | * 547 | * @param element 548 | * Element to be updated 549 | * @throws LibOperaLinkException 550 | * thrown when the server communication exception occurs or 551 | * folder parameter is not a folder element 552 | */ 553 | public void update(Element element) throws LinkItemNotFound, 554 | LinkAccessDeniedException, LinkResponseFormatException, 555 | LibOperaLinkException { 556 | HashMap params = element.createParamsDict(); 557 | params.put(ApiParameters.API_METHOD_PARAM, ApiParameters.UPDATE); 558 | 559 | JSONArray arrayResponse = makeRequest(createUrl(element.getDatatype(), 560 | element.getId()), HttpClient3.POST, params); 561 | try { 562 | element.updateParameters(arrayResponse.getJSONObject(0)); 563 | } catch (JSONException e) { 564 | e.printStackTrace(); 565 | throw new LibOperaLinkException(e); 566 | } 567 | } 568 | 569 | /** 570 | * Removes the element from Opera Link server 571 | * 572 | * @param element 573 | * Element to be removed 574 | * @throws LibOperaLinkException 575 | */ 576 | public void delete(Element element) throws LinkItemNotFound, 577 | LinkAccessDeniedException, LinkResponseFormatException, 578 | LibOperaLinkException { 579 | delete(element.getId(), element.getDatatype()); 580 | } 581 | 582 | /** 583 | * Removes the element from Opera Link server 584 | * 585 | * @param id 586 | * ID of element to be removed 587 | * @param datatype 588 | * Datatype of element to be removed 589 | * @throws LibOperaLinkException 590 | */ 591 | public void delete(String id, String datatype) throws LinkItemNotFound, 592 | LinkAccessDeniedException, LinkResponseFormatException, 593 | LibOperaLinkException { 594 | HashMap params = new HashMap() { 595 | private static final long serialVersionUID = 1L; 596 | { 597 | put(ApiParameters.API_METHOD_PARAM, ApiParameters.DELETE); 598 | } 599 | }; 600 | makeRequest(createUrl(datatype, id), HttpClient3.POST, params); 601 | } 602 | 603 | /** 604 | * Moves the element to trash folder 605 | * 606 | * @param id 607 | * ID of element to be trashed 608 | * @param datatype 609 | * Datatype of element to be trashed 610 | * @throws LibOperaLinkException 611 | */ 612 | public void trash(String id, String datatype) throws LinkItemNotFound, 613 | LinkAccessDeniedException, LinkResponseFormatException, 614 | LibOperaLinkException { 615 | HashMap params = new HashMap() { 616 | private static final long serialVersionUID = 1L; 617 | { 618 | put(ApiParameters.API_METHOD_PARAM, ApiParameters.TRASH); 619 | } 620 | }; 621 | makeRequest(createUrl(datatype, id), HttpClient3.POST, params); 622 | } 623 | 624 | /** 625 | * Moves the element to trash folder 626 | * 627 | * @param 628 | * Type of the element - a subclass of FolderEntry 629 | * @param element 630 | * Element to be trashed 631 | * @throws LibOperaLinkException 632 | */ 633 | public > void trash(FolderEntry element) 634 | throws LinkItemNotFound, LinkAccessDeniedException, 635 | LinkResponseFormatException, LibOperaLinkException { 636 | trash(element.getId(), element.getDatatype()); 637 | } 638 | 639 | /** 640 | * Moves the element into the specified folder at the last position 641 | * 642 | * @param 643 | * Type of the element - a subclass of FolderEntry 644 | * @param element 645 | * Element to be moved 646 | * @param folder 647 | * Destination folder 648 | */ 649 | public , U extends FolderInterface> void moveInsideFolder( 650 | FolderEntry element, U folderElement) throws LinkItemNotFound, 651 | LinkAccessDeniedException, LinkResponseFormatException, 652 | LibOperaLinkException { 653 | 654 | if (folderElement == null || folderElement.getId() == null) { 655 | throw new LinkItemNotFound(); 656 | } 657 | moveInsideFolder(element.getId(), folderElement.getId(), element 658 | .getDatatype()); 659 | } 660 | 661 | /** 662 | * Moves the element into the root folder, it is appended at the last 663 | * position 664 | * 665 | * @param 666 | * Type of the element - a subclass of FolderEntry 667 | * @param element 668 | * Element to be moved 669 | */ 670 | public > void moveToRootFolder( 671 | FolderEntry element) throws LinkItemNotFound, 672 | LinkAccessDeniedException, LinkResponseFormatException, 673 | LibOperaLinkException { 674 | 675 | moveInsideFolder(element.getId(), "", element.getDatatype()); 676 | } 677 | 678 | /** 679 | * Moves the element into the specified folder (element with 680 | * ID=referenceItem) at the last position 681 | * 682 | * @param id 683 | * ID of the element to be moved 684 | * @param referenceItem 685 | * ID of the destination folder, if null then the element is 686 | * moved to a root folder 687 | * @param datatype 688 | * Datatype of the element and folder 689 | * @throws LibOperaLinkException 690 | */ 691 | public void moveInsideFolder(String id, String referenceItem, 692 | String datatype) throws LinkItemNotFound, 693 | LinkAccessDeniedException, LinkResponseFormatException, 694 | LibOperaLinkException { 695 | move(id, referenceItem, datatype, ApiParameters.MOVE_POSITION_INTO); 696 | } 697 | 698 | /** 699 | * Moves the id element to position relative to the referenceItem folder 700 | * 701 | * @param referenceItem 702 | * if null then the element is moved to a root folder 703 | * @throws LibOperaLinkException 704 | */ 705 | private void move(String id, String referenceItem, String datatype, 706 | final String relativePosition) throws LinkItemNotFound, 707 | LinkAccessDeniedException, LinkResponseFormatException, 708 | LibOperaLinkException { 709 | final String requestUrl = createUrl(datatype, id); 710 | final String referenceItemID = referenceItem != null ? referenceItem 711 | : ""; 712 | HashMap params = new HashMap() { 713 | private static final long serialVersionUID = 1L; 714 | { 715 | put(ApiParameters.API_METHOD_PARAM, ApiParameters.MOVE); 716 | put(ApiParameters.MOVE_REFERENCE_ITEM_PARAM, referenceItemID); 717 | put(ApiParameters.MOVE_RELATIVE_POSITION_PARAM, 718 | relativePosition); 719 | } 720 | }; 721 | makeRequest(requestUrl, HttpClient3.POST, params); 722 | } 723 | 724 | /** 725 | * Moves the element just after the specified referenceItem 726 | * 727 | * @param 728 | * Type of the element - a subclass of FolderEntry 729 | * @param element 730 | * Element to be moved 731 | * @param referenceItem 732 | * Element which is going to be followed by the moved element 733 | * @throws LibOperaLinkException 734 | */ 735 | public > void moveAfterElement( 736 | FolderEntry element, FolderEntry referenceItem) 737 | throws LinkItemNotFound, LinkAccessDeniedException, 738 | LinkResponseFormatException, LibOperaLinkException { 739 | moveAfterElement(element.getId(), referenceItem.getId(), element 740 | .getDatatype()); 741 | } 742 | 743 | /** 744 | * Moves the element with ID=elId just after the specified element with 745 | * ID=referenceElId 746 | * 747 | * @param elId 748 | * ID of an element to be moved 749 | * @param referenceElId 750 | * ID of an element which is going to be followed by the moved 751 | * element 752 | * @param datatype 753 | * Datatype of the element and folder 754 | * @throws LibOperaLinkException 755 | */ 756 | public void moveAfterElement(String elId, String referenceElId, 757 | String datatype) throws LinkItemNotFound, 758 | LinkAccessDeniedException, LinkResponseFormatException, 759 | LibOperaLinkException { 760 | move(elId, referenceElId, datatype, ApiParameters.MOVE_POSITION_AFTER); 761 | } 762 | 763 | /** 764 | * Moves the element with ID=elId just before the specified element with 765 | * ID=referenceElId 766 | * 767 | * @param Element 768 | * to be moved 769 | * @param Element 770 | * which is going to be proceeded by the moved element 771 | * @throws LibOperaLinkException 772 | */ 773 | public > void moveBeforeElement( 774 | FolderEntry element, FolderEntry referenceItem) 775 | throws LinkItemNotFound, LinkAccessDeniedException, 776 | LinkResponseFormatException, LibOperaLinkException { 777 | moveBeforeElement(element.getId(), referenceItem.getId(), element 778 | .getDatatype()); 779 | } 780 | 781 | /** 782 | * Moves the element with ID=elId just before the specified element with 783 | * ID=referenceElId 784 | * 785 | * @param elId 786 | * ID of an element to be moved 787 | * @param referenceElId 788 | * ID of an element which is going to be proceeded by the moved 789 | * element 790 | * @param datatype 791 | * Datatype of the element and folder 792 | * @throws LibOperaLinkException 793 | */ 794 | public void moveBeforeElement(String elId, String referenceElId, 795 | String datatype) throws LinkItemNotFound, 796 | LinkAccessDeniedException, LinkResponseFormatException, 797 | LibOperaLinkException { 798 | move(elId, referenceElId, datatype, ApiParameters.MOVE_POSITION_BEFORE); 799 | } 800 | } 801 | -------------------------------------------------------------------------------- /src/main/java/com/opera/link/apilib/exceptions/LibOperaLinkException.java: -------------------------------------------------------------------------------- 1 | package com.opera.link.apilib.exceptions; 2 | 3 | 4 | 5 | public class LibOperaLinkException extends Exception { 6 | 7 | public static final int BAD_REQUEST = 400; 8 | public static final int UNAUTHORIZED = 401; 9 | public static final int NOT_FOUND = 404; 10 | 11 | 12 | private static final long serialVersionUID = 1L; 13 | public LibOperaLinkException(Exception innerException) { 14 | super(); 15 | } 16 | public LibOperaLinkException(String message) { 17 | super(message); 18 | } 19 | public static void throwCommunicationExeption(int httpStatusCode, Exception innerException) 20 | throws LinkItemNotFound, LinkAccessDeniedException, LibOperaLinkException { 21 | switch(httpStatusCode) { 22 | case NOT_FOUND: 23 | throw new LinkItemNotFound(); 24 | case UNAUTHORIZED: 25 | throw new LinkAccessDeniedException(innerException); 26 | } 27 | throw new LibOperaLinkException(innerException); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/opera/link/apilib/exceptions/LinkAccessDeniedException.java: -------------------------------------------------------------------------------- 1 | package com.opera.link.apilib.exceptions; 2 | 3 | public class LinkAccessDeniedException extends LibOperaLinkException { 4 | 5 | 6 | public LinkAccessDeniedException(Exception innerException) { 7 | super(innerException); 8 | } 9 | 10 | private static final long serialVersionUID = 1L; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/opera/link/apilib/exceptions/LinkItemNotFound.java: -------------------------------------------------------------------------------- 1 | package com.opera.link.apilib.exceptions; 2 | 3 | 4 | 5 | 6 | public class LinkItemNotFound extends LibOperaLinkException { 7 | 8 | public LinkItemNotFound() { 9 | super("Requested item with id given does not exist on the server"); 10 | } 11 | 12 | /** 13 | * 14 | */ 15 | private static final long serialVersionUID = 1L; 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/opera/link/apilib/exceptions/LinkResponseFormatException.java: -------------------------------------------------------------------------------- 1 | package com.opera.link.apilib.exceptions; 2 | 3 | 4 | public class LinkResponseFormatException extends LibOperaLinkException { 5 | 6 | private static final long serialVersionUID = 1L; 7 | 8 | public LinkResponseFormatException(Exception innerException) { 9 | super(innerException); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/opera/link/apilib/items/Bookmark.java: -------------------------------------------------------------------------------- 1 | package com.opera.link.apilib.items; 2 | 3 | import java.io.IOException; 4 | import java.util.Date; 5 | import java.util.HashMap; 6 | 7 | import org.json.JSONException; 8 | import org.json.JSONObject; 9 | 10 | import com.opera.link.apilib.Base64; 11 | 12 | 13 | public class Bookmark extends BookmarkFolderEntry { 14 | 15 | public static final String ITEM_TYPE = "bookmark"; 16 | 17 | public String title; 18 | public String uri; 19 | 20 | public String nickname; 21 | public String description; 22 | public byte[] icon; 23 | public Date visited; 24 | public Date created; 25 | 26 | protected Bookmark() { 27 | 28 | } 29 | 30 | public Bookmark(String title, String uri) { 31 | this(); 32 | this.title = title; 33 | this.uri = uri; 34 | this.created = new Date(); 35 | } 36 | 37 | @Override 38 | void loadParameters(JSONObject json) throws JSONException { 39 | if (json.has(Element.TITLE_JSON_FIELD)) { 40 | title = json.getString(Element.TITLE_JSON_FIELD); 41 | } 42 | if (json.has(Element.URI_JSON_FIELD)) { 43 | uri = json.getString(Element.URI_JSON_FIELD); 44 | } 45 | if (json.has(Element.NICKNAME_JSON_FIELD)) { 46 | nickname = json.getString(Element.NICKNAME_JSON_FIELD); 47 | } 48 | if (json.has(Element.DESCRIPTION_JSON_FIELD)) { 49 | description = json.getString(Element.DESCRIPTION_JSON_FIELD); 50 | } 51 | if (json.has(Element.ICON_JSON_FIELD)) { 52 | 53 | try { 54 | icon = Base64.decode(json.getString(Element.ICON_JSON_FIELD), Base64.NO_OPTIONS); 55 | } catch (IOException e) { 56 | e.printStackTrace(); 57 | icon = null; 58 | } 59 | } 60 | if (json.has(Element.CREATED_JSON_FIELD)) { 61 | created = parseDate(json.getString(Element.CREATED_JSON_FIELD)); 62 | } 63 | if (json.has(Element.VISITED_JSON_FIELD)) { 64 | visited = parseDate(json.getString(Element.VISITED_JSON_FIELD)); 65 | } 66 | } 67 | 68 | @Override 69 | public boolean isBookmark() { 70 | return true; 71 | } 72 | 73 | @Override 74 | public HashMap createParamsDict() { 75 | final String iconString; 76 | if (icon != null) { 77 | iconString = Base64.encodeBytes(icon); 78 | } else { 79 | iconString = null; 80 | } 81 | final String createdString; 82 | if (created != null) { 83 | createdString = dateToString(created); 84 | } else { 85 | createdString = null; 86 | } 87 | final String visitedString; 88 | if (visited != null) { 89 | visitedString = dateToString(created); 90 | } else { 91 | visitedString = null; 92 | } 93 | 94 | HashMap params = new HashMap() { 95 | private static final long serialVersionUID = 1L; 96 | 97 | { 98 | put(Element.TITLE_JSON_FIELD, title); 99 | put(Element.URI_JSON_FIELD, uri); 100 | put(Element.NICKNAME_JSON_FIELD, nickname); 101 | put(Element.DESCRIPTION_JSON_FIELD, description); 102 | put(Element.ICON_JSON_FIELD, iconString); 103 | put(Element.CREATED_JSON_FIELD, createdString); 104 | put(Element.VISITED_JSON_FIELD, visitedString); 105 | } 106 | }; 107 | return skipNullParams(params); 108 | } 109 | 110 | @Override 111 | public String getItemType() { 112 | return ITEM_TYPE; 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/com/opera/link/apilib/items/BookmarkFolder.java: -------------------------------------------------------------------------------- 1 | package com.opera.link.apilib.items; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | 6 | import org.json.JSONException; 7 | import org.json.JSONObject; 8 | 9 | public class BookmarkFolder extends BookmarkFolderEntry implements FolderInterface { 10 | 11 | protected static final String ITEM_TYPE = "bookmark_folder"; 12 | 13 | protected FolderContext folder = new FolderContext(); 14 | 15 | public String title; 16 | 17 | public String nickname; 18 | public String description; 19 | public String target; 20 | 21 | protected BookmarkFolder() { 22 | 23 | } 24 | 25 | public BookmarkFolder(String title) { 26 | this(); 27 | this.title = title; 28 | } 29 | 30 | @Override 31 | public boolean isFolder() { 32 | return true; 33 | } 34 | 35 | public ArrayList getChildren() { 36 | if (folder != null) { 37 | return this.folder.getChildren(); 38 | } 39 | return null; 40 | } 41 | 42 | 43 | @Override 44 | void loadParameters(JSONObject json) throws JSONException { 45 | folder.loadTargetFolderProperties(json); 46 | 47 | if (json.has(Element.TITLE_JSON_FIELD)) { 48 | title = json.getString(Element.TITLE_JSON_FIELD); 49 | } 50 | if (json.has(Element.NICKNAME_JSON_FIELD)) { 51 | nickname = json.getString(Element.NICKNAME_JSON_FIELD); 52 | } 53 | if (json.has(Element.DESCRIPTION_JSON_FIELD)) { 54 | description = json.getString(Element.DESCRIPTION_JSON_FIELD); 55 | } 56 | if (json.has(Element.TARGET_JSON_FIELD)) { 57 | target = json.getString(Element.TARGET_JSON_FIELD); 58 | } 59 | if (json.has(Element.TYPE_JSON_FIELD)) { 60 | folder.setType(json.getString(Element.TYPE_JSON_FIELD)); 61 | } 62 | 63 | } 64 | 65 | @Override 66 | public HashMap createParamsDict() { 67 | HashMap params = new HashMap() { 68 | private static final long serialVersionUID = 1L; 69 | { 70 | put(Element.TITLE_JSON_FIELD, title); 71 | put(Element.NICKNAME_JSON_FIELD, nickname); 72 | put(Element.DESCRIPTION_JSON_FIELD, description); 73 | } 74 | }; 75 | return skipNullParams(params); 76 | } 77 | 78 | @Override 79 | public String getItemType() { 80 | return ITEM_TYPE; 81 | } 82 | 83 | public FolderContext getFolderContext() { 84 | return this.folder; 85 | } 86 | 87 | public boolean isTrash() { 88 | return this.folder.isTrash(); 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/com/opera/link/apilib/items/BookmarkFolderEntry.java: -------------------------------------------------------------------------------- 1 | package com.opera.link.apilib.items; 2 | 3 | 4 | public abstract class BookmarkFolderEntry extends FolderEntry { 5 | 6 | public static final String DATATYPE = "bookmark"; 7 | 8 | 9 | @Override 10 | public String getDatatype() { 11 | return DATATYPE; 12 | } 13 | 14 | public boolean isBookmark() { 15 | return false; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/opera/link/apilib/items/BookmarkSeparator.java: -------------------------------------------------------------------------------- 1 | package com.opera.link.apilib.items; 2 | 3 | import java.util.HashMap; 4 | 5 | import org.json.JSONException; 6 | import org.json.JSONObject; 7 | 8 | public class BookmarkSeparator extends BookmarkFolderEntry { 9 | 10 | public static final String ITEM_TYPE = "bookmark_separator"; 11 | 12 | public BookmarkSeparator() { 13 | 14 | } 15 | 16 | @Override 17 | void loadParameters(JSONObject json) throws JSONException { 18 | 19 | } 20 | 21 | @Override 22 | public HashMap createParamsDict() { 23 | return new HashMap(); 24 | } 25 | 26 | @Override 27 | public String getItemType() { 28 | return ITEM_TYPE; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/opera/link/apilib/items/Element.java: -------------------------------------------------------------------------------- 1 | package com.opera.link.apilib.items; 2 | 3 | import java.text.ParseException; 4 | import java.text.SimpleDateFormat; 5 | import java.util.ArrayList; 6 | import java.util.Date; 7 | import java.util.HashMap; 8 | import java.util.Map.Entry; 9 | 10 | import org.json.JSONArray; 11 | import org.json.JSONException; 12 | import org.json.JSONObject; 13 | 14 | public abstract class Element { 15 | 16 | private String id = null; 17 | 18 | /** 19 | * @return Returns unique ID of the element at the server, 20 | * if the element not added then returns null 21 | */ 22 | public String getId() { 23 | return id; 24 | } 25 | public void setId(String id) { 26 | this.id = id; 27 | } 28 | 29 | protected static String ITEM_TYPE; 30 | 31 | protected static final String PROPERTIES_JSON_KEY = "properties"; 32 | public static final String ITEM_TYPE_JSON_KEY = "item_type"; 33 | protected static final String ID_JSON_KEY = "id"; 34 | protected static final String CHILDREN_JSON_KEY = "children"; 35 | 36 | protected static final String TITLE_JSON_FIELD = "title"; 37 | protected static final String NICKNAME_JSON_FIELD = "nickname"; 38 | protected static final String DESCRIPTION_JSON_FIELD = "description"; 39 | protected static final String URI_JSON_FIELD = "uri"; 40 | protected static final String CREATED_JSON_FIELD = "created"; 41 | protected static final String VISITED_JSON_FIELD = "visited"; 42 | protected static final String ICON_JSON_FIELD = "icon"; 43 | protected static final String THUMBNAIL_JSON_FIELD = "thumbnail"; 44 | protected static final String TARGET_JSON_FIELD = "target"; 45 | protected static final String TYPE_JSON_FIELD = "type"; 46 | protected static final String CONTENT_JSON_FIELD = "content"; 47 | protected static final String ENCODING_JSON_FIELD = "encoding"; 48 | protected static final String ISPOST_JSON_FIELD = "is_post"; 49 | protected static final String POSTQUERY_JSON_FIELD = "post_query"; 50 | protected static final String KEY_JSON_FIELD = "key"; 51 | 52 | 53 | abstract void loadParameters(JSONObject json) throws JSONException; 54 | 55 | public abstract String getDatatype(); 56 | public abstract String getItemType(); 57 | 58 | protected static final SimpleDateFormat timezoneDateFormat = 59 | new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); 60 | protected static final SimpleDateFormat localDateFormat = 61 | new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); 62 | 63 | protected static > void createChildren(JSONObject jsonElement, 64 | FolderContext folderContent, Class cls) throws JSONException { 65 | 66 | if (jsonElement.has(Element.CHILDREN_JSON_KEY)) { 67 | folderContent.setChildren( 68 | elementsFactory(jsonElement.getJSONArray(Element.CHILDREN_JSON_KEY), cls) 69 | ); 70 | } else { 71 | folderContent.setChildren(null); 72 | } 73 | } 74 | 75 | 76 | @SuppressWarnings("unchecked") 77 | public static ArrayList elementsFactory(JSONArray jsonList, Class cls) throws JSONException { 78 | int size = jsonList.length(); 79 | ArrayList elements = new ArrayList(size); 80 | 81 | for (int i = 0; i < size; i++) { 82 | 83 | JSONObject jsonElement = (JSONObject) jsonList.get(i); 84 | T element = null; 85 | 86 | // create object according to the item_type 87 | String item_type = jsonElement.getString(Element.ITEM_TYPE_JSON_KEY); 88 | if (item_type.equals(Bookmark.ITEM_TYPE)) { 89 | element = (T) new Bookmark(); 90 | } 91 | if (item_type.equals(BookmarkFolder.ITEM_TYPE)) { 92 | BookmarkFolder bookmarkFolder = new BookmarkFolder(); 93 | element = (T) bookmarkFolder; 94 | // read folder content 95 | createChildren(jsonElement, bookmarkFolder.folder, BookmarkFolderEntry.class); 96 | } 97 | if (item_type.equals(BookmarkSeparator.ITEM_TYPE)) { 98 | element = (T) new BookmarkSeparator(); 99 | } 100 | if (item_type.equals(Note.ITEM_TYPE)) { 101 | element = (T) new Note(); 102 | } 103 | if (item_type.equals(NoteFolder.ITEM_TYPE)) { 104 | NoteFolder noteFolder = new NoteFolder(); 105 | element = (T) noteFolder; 106 | // read folder content 107 | createChildren(jsonElement, noteFolder.folder, NoteFolderEntry.class); 108 | } 109 | if (item_type.equals(NoteSeparator.ITEM_TYPE)) { 110 | element = (T) new NoteSeparator(); 111 | } 112 | if (item_type.equals(SpeedDial.ITEM_TYPE)) { 113 | element = (T) new SpeedDial(); 114 | } 115 | if (item_type.equals(UrlFilter.ITEM_TYPE)) { 116 | element = (T) new UrlFilter(); 117 | } 118 | if (item_type.equals(SearchEngine.ITEM_TYPE)) { 119 | element = (T) new SearchEngine(); 120 | } 121 | 122 | // set properties of new created object 123 | elements.add(element); 124 | 125 | element.updateParameters(jsonElement); 126 | } 127 | 128 | return elements; 129 | } 130 | 131 | public void updateParameters(JSONObject jsonElement) throws JSONException { 132 | this.id = jsonElement.getString(ID_JSON_KEY); 133 | this.loadParameters(jsonElement.getJSONObject(Element.PROPERTIES_JSON_KEY)); 134 | } 135 | 136 | public Date parseDate(String dateString) { 137 | try { 138 | return timezoneDateFormat.parse(dateString); 139 | } catch (ParseException e) { 140 | try { // try parse without a time zone 141 | return localDateFormat.parse(dateString); 142 | } catch (ParseException e1) { 143 | e.printStackTrace(); 144 | return null; 145 | } 146 | } 147 | } 148 | 149 | public String dateToString(Date date) { 150 | if (date != null) { 151 | return timezoneDateFormat.format(date); 152 | } 153 | return null; 154 | } 155 | 156 | public abstract HashMap createParamsDict(); 157 | 158 | protected HashMap skipNullParams( 159 | HashMap params) { 160 | HashMap filteredParams = new HashMap(); 161 | for (Entry paramEntry : params.entrySet()) { 162 | if (paramEntry.getValue() != null) { 163 | filteredParams.put(paramEntry.getKey(), paramEntry.getValue()); 164 | } 165 | } 166 | return filteredParams; 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/main/java/com/opera/link/apilib/items/FolderContext.java: -------------------------------------------------------------------------------- 1 | package com.opera.link.apilib.items; 2 | import java.util.ArrayList; 3 | 4 | import org.json.JSONException; 5 | import org.json.JSONObject; 6 | 7 | 8 | 9 | public class FolderContext > { 10 | protected static final String TRASH_FOLDER_TYPE = "trash"; 11 | 12 | protected static final String TARGET_PROPERTY_KEY = "target"; 13 | protected static final String TARGET_MINI = "mini"; 14 | 15 | protected static final String MOVE_IS_COPY_FOLDER = "move_is_copy"; 16 | protected static final String DELETABLE_FOLDER = "deletable"; 17 | protected static final String ALLOWS_SUB_FOLDERS = "sub_folders_allowed"; 18 | protected static final String ALLOWS_SEPARATORS = "separators_allowed"; 19 | protected static final String MAX_ITEMS = "max_items"; 20 | 21 | private ArrayList children; 22 | 23 | /** 24 | * @return {@code true} if the content of this folder is displayed on Opera Mini 25 | */ 26 | public boolean isTargetMini() { 27 | return isTargetMini; 28 | } 29 | 30 | 31 | /** 32 | * @return {@code true} if moving to this folder results in copying the moved element 33 | */ 34 | public boolean moveIsCopy() { 35 | return moveIsCopy; 36 | } 37 | 38 | /** 39 | * @return {@code true} if it is allowed to delete the content of this folder 40 | */ 41 | public boolean isDeletable() { 42 | return isDeletable; 43 | } 44 | 45 | /** 46 | * @return {@code true} if it is allowed to add folders to the content of this folder 47 | */ 48 | public boolean allowsSubFolders() { 49 | return allowsSubFolders; 50 | } 51 | 52 | /** 53 | * @return {@code true} if it is allowed to add separators to the content of this folder 54 | */ 55 | public boolean allowsSeparators() { 56 | return allowsSeparators; 57 | } 58 | 59 | /** 60 | * Specifies maximal number of items that this folder can contain. 61 | * If there is no such limit then returns 0. 62 | */ 63 | public int getMaxItems() { 64 | return maxItems; 65 | } 66 | 67 | private boolean isTargetMini = false; 68 | private boolean isTrash = false; 69 | private boolean moveIsCopy = false; 70 | private boolean isDeletable = true; 71 | private boolean allowsSubFolders = true; 72 | private boolean allowsSeparators = true; 73 | private int maxItems = 0; // when 0, then there is no limit on number of items 74 | 75 | public void setType(String type) { 76 | this.isTrash = TRASH_FOLDER_TYPE.equals(type); 77 | } 78 | 79 | public ArrayList getChildren() { 80 | return this.children; 81 | } 82 | 83 | public boolean isTrash() { 84 | return isTrash; 85 | } 86 | 87 | public void setChildren(ArrayList children) { 88 | if (children == null) { 89 | this.children = new ArrayList(); 90 | } else { 91 | this.children = children; 92 | } 93 | } 94 | 95 | public void loadTargetFolderProperties(JSONObject properties) throws JSONException { 96 | if (properties == null || !properties.has(TARGET_PROPERTY_KEY)) { 97 | return; 98 | } 99 | if (!properties.getString(TARGET_PROPERTY_KEY).equals(TARGET_MINI)) { 100 | return; 101 | } 102 | 103 | isTargetMini = true; 104 | if (properties.has(MOVE_IS_COPY_FOLDER)) { 105 | moveIsCopy = properties.getBoolean(MOVE_IS_COPY_FOLDER); 106 | } 107 | if (properties.has(DELETABLE_FOLDER)) { 108 | isDeletable = properties.getBoolean(DELETABLE_FOLDER); 109 | } 110 | if (properties.has(ALLOWS_SUB_FOLDERS)) { 111 | allowsSubFolders = properties.getBoolean(ALLOWS_SUB_FOLDERS); 112 | } 113 | if (properties.has(ALLOWS_SEPARATORS)) { 114 | allowsSeparators = properties.getBoolean(ALLOWS_SEPARATORS); 115 | } 116 | if (properties.has(MAX_ITEMS)) { 117 | maxItems = properties.getInt(MAX_ITEMS); 118 | } 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/com/opera/link/apilib/items/FolderEntry.java: -------------------------------------------------------------------------------- 1 | package com.opera.link.apilib.items; 2 | 3 | 4 | public abstract class FolderEntry > extends Element { 5 | 6 | public boolean isFolder() { 7 | return false; 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/opera/link/apilib/items/FolderInterface.java: -------------------------------------------------------------------------------- 1 | package com.opera.link.apilib.items; 2 | 3 | import java.util.ArrayList; 4 | 5 | public interface FolderInterface > { 6 | 7 | boolean isTrash(); 8 | 9 | ArrayList getChildren(); 10 | 11 | FolderContext getFolderContext(); 12 | 13 | String getId(); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/opera/link/apilib/items/Note.java: -------------------------------------------------------------------------------- 1 | package com.opera.link.apilib.items; 2 | 3 | import java.util.Date; 4 | import java.util.HashMap; 5 | 6 | import org.json.JSONException; 7 | import org.json.JSONObject; 8 | 9 | public class Note extends NoteFolderEntry { 10 | 11 | public static final String ITEM_TYPE = "note"; 12 | 13 | public String content; 14 | 15 | public Date created; 16 | public String uri; 17 | 18 | protected Note() { 19 | 20 | } 21 | 22 | public Note(String content) { 23 | this(); 24 | this.content = content; 25 | this.created = new Date(); 26 | } 27 | 28 | @Override 29 | void loadParameters(JSONObject json) throws JSONException { 30 | if (json.has(Element.CONTENT_JSON_FIELD)) { 31 | content = json.getString(Element.CONTENT_JSON_FIELD); 32 | } 33 | if (json.has(Element.URI_JSON_FIELD)) { 34 | uri = json.getString(Element.URI_JSON_FIELD); 35 | } 36 | if (json.has(Element.CREATED_JSON_FIELD)) { 37 | created = parseDate(json.getString(Element.CREATED_JSON_FIELD)); 38 | } 39 | } 40 | 41 | @Override 42 | public HashMap createParamsDict() { 43 | final String createdString; 44 | if (created != null) { 45 | createdString = dateToString(created); 46 | } else { 47 | createdString = null; 48 | } 49 | 50 | HashMap params = new HashMap() { 51 | private static final long serialVersionUID = 1L; 52 | 53 | { 54 | put(Element.CONTENT_JSON_FIELD, content); 55 | put(Element.URI_JSON_FIELD, uri); 56 | put(Element.CREATED_JSON_FIELD, createdString); 57 | } 58 | }; 59 | return skipNullParams(params); 60 | } 61 | 62 | @Override 63 | public String getItemType() { 64 | return ITEM_TYPE; 65 | } 66 | 67 | @Override 68 | public boolean isNote() { 69 | return true; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/opera/link/apilib/items/NoteFolder.java: -------------------------------------------------------------------------------- 1 | package com.opera.link.apilib.items; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | 6 | import org.json.JSONException; 7 | import org.json.JSONObject; 8 | 9 | public class NoteFolder extends NoteFolderEntry implements FolderInterface { 10 | 11 | protected static final String ITEM_TYPE = "note_folder"; 12 | 13 | protected FolderContext folder = new FolderContext(); 14 | 15 | public String title; 16 | 17 | public String target; 18 | 19 | protected NoteFolder() { 20 | 21 | } 22 | 23 | public NoteFolder(String title) { 24 | this(); 25 | this.title = title; 26 | } 27 | 28 | @Override 29 | public boolean isFolder() { 30 | return true; 31 | } 32 | 33 | 34 | @Override 35 | void loadParameters(JSONObject json) throws JSONException { 36 | folder.loadTargetFolderProperties(json); 37 | 38 | if (json.has(Element.TITLE_JSON_FIELD)) { 39 | title = json.getString(Element.TITLE_JSON_FIELD); 40 | } 41 | if (json.has(Element.TARGET_JSON_FIELD)) { 42 | target = json.getString(Element.TARGET_JSON_FIELD); 43 | } 44 | if (json.has(Element.TYPE_JSON_FIELD)) { 45 | this.folder.setType(json.getString(Element.TYPE_JSON_FIELD)); 46 | } 47 | } 48 | 49 | @Override 50 | public HashMap createParamsDict() { 51 | HashMap params = new HashMap() { 52 | private static final long serialVersionUID = 1L; 53 | { 54 | put(Element.TITLE_JSON_FIELD, title); 55 | } 56 | }; 57 | return skipNullParams(params); 58 | } 59 | 60 | @Override 61 | public String getItemType() { 62 | return ITEM_TYPE; 63 | } 64 | 65 | public ArrayList getChildren() { 66 | return this.folder.getChildren(); 67 | } 68 | 69 | public FolderContext getFolderContext() { 70 | return this.folder; 71 | } 72 | 73 | public boolean isTrash() { 74 | return this.folder.isTrash(); 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/com/opera/link/apilib/items/NoteFolderEntry.java: -------------------------------------------------------------------------------- 1 | package com.opera.link.apilib.items; 2 | 3 | 4 | public abstract class NoteFolderEntry extends FolderEntry { 5 | 6 | 7 | public static final String DATATYPE = "note"; 8 | 9 | @Override 10 | public String getDatatype() { 11 | return DATATYPE; 12 | } 13 | 14 | public boolean isNote() { 15 | return false; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/opera/link/apilib/items/NoteSeparator.java: -------------------------------------------------------------------------------- 1 | package com.opera.link.apilib.items; 2 | 3 | import java.util.HashMap; 4 | 5 | import org.json.JSONException; 6 | import org.json.JSONObject; 7 | 8 | public class NoteSeparator extends NoteFolderEntry { 9 | 10 | public static final String ITEM_TYPE = "note_separator"; 11 | 12 | public NoteSeparator() { 13 | 14 | } 15 | 16 | @Override 17 | void loadParameters(JSONObject json) throws JSONException { 18 | 19 | } 20 | 21 | @Override 22 | public HashMap createParamsDict() { 23 | return new HashMap(); 24 | } 25 | 26 | @Override 27 | public String getItemType() { 28 | return ITEM_TYPE; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/opera/link/apilib/items/SearchEngine.java: -------------------------------------------------------------------------------- 1 | package com.opera.link.apilib.items; 2 | 3 | import java.io.IOException; 4 | import java.util.HashMap; 5 | import org.json.JSONException; 6 | import org.json.JSONObject; 7 | import com.opera.link.apilib.Base64; 8 | 9 | public class SearchEngine extends Element { 10 | 11 | public static final String ITEM_TYPE = "search_engine"; 12 | 13 | public String title; 14 | public String uri; 15 | public String encoding; 16 | 17 | public boolean is_post; 18 | public String post_query; 19 | public byte[] icon; 20 | public String key; 21 | 22 | protected SearchEngine() { 23 | 24 | } 25 | 26 | public SearchEngine(String title, String uri) { 27 | this(); 28 | this.title = title; 29 | this.uri = uri; 30 | } 31 | 32 | @Override 33 | void loadParameters(JSONObject json) throws JSONException { 34 | if (json.has(Element.TITLE_JSON_FIELD)) { 35 | title = json.getString(Element.TITLE_JSON_FIELD); 36 | } 37 | if (json.has(Element.URI_JSON_FIELD)) { 38 | uri = json.getString(Element.URI_JSON_FIELD); 39 | } 40 | if (json.has(Element.ENCODING_JSON_FIELD)) { 41 | encoding = json.getString(Element.ENCODING_JSON_FIELD); 42 | } 43 | if (json.has(Element.ISPOST_JSON_FIELD)) { 44 | is_post = Boolean.valueOf(json.getString(Element.ISPOST_JSON_FIELD)); 45 | } 46 | if (json.has(Element.POSTQUERY_JSON_FIELD)) { 47 | post_query = json.getString(Element.POSTQUERY_JSON_FIELD); 48 | } 49 | if (json.has(Element.KEY_JSON_FIELD)) { 50 | key = json.getString(Element.KEY_JSON_FIELD); 51 | } 52 | if (json.has(Element.ICON_JSON_FIELD)) { 53 | try { 54 | icon = Base64.decode(json.getString(Element.ICON_JSON_FIELD), Base64.NO_OPTIONS); 55 | } catch (IOException e) { 56 | e.printStackTrace(); 57 | icon = null; 58 | } 59 | } 60 | } 61 | 62 | @Override 63 | public HashMap createParamsDict() { 64 | 65 | final String iconString; 66 | if (icon != null) { 67 | iconString = Base64.encodeBytes(icon); 68 | } else { 69 | iconString = null; 70 | } 71 | 72 | HashMap params = new HashMap() { 73 | private static final long serialVersionUID = 1L; 74 | { 75 | put(Element.TITLE_JSON_FIELD, title); 76 | put(Element.URI_JSON_FIELD, uri); 77 | put(Element.ENCODING_JSON_FIELD, encoding); 78 | put(Element.ISPOST_JSON_FIELD, new Boolean(is_post).toString()); 79 | put(Element.POSTQUERY_JSON_FIELD, post_query); 80 | put(Element.KEY_JSON_FIELD, key); 81 | put(Element.ICON_JSON_FIELD, iconString); 82 | } 83 | }; 84 | return skipNullParams(params); 85 | } 86 | 87 | public String getTitle() { 88 | return title; 89 | } 90 | 91 | public void setTitle(String title) { 92 | this.title = title; 93 | } 94 | 95 | public String getUri() { 96 | return uri; 97 | } 98 | 99 | public void setUri(String uri) { 100 | this.uri = uri; 101 | } 102 | 103 | public String getEncoding() { 104 | return encoding; 105 | } 106 | 107 | public void setEncoding(String encoding) { 108 | this.encoding = encoding; 109 | } 110 | 111 | public boolean isIs_post() { 112 | return is_post; 113 | } 114 | 115 | public void setIs_post(boolean isPost) { 116 | is_post = isPost; 117 | } 118 | 119 | public String getPost_query() { 120 | return post_query; 121 | } 122 | 123 | public void setPost_query(String postQuery) { 124 | post_query = postQuery; 125 | } 126 | 127 | public byte[] getIcon() { 128 | return icon; 129 | } 130 | 131 | public void setIcon(byte[] icon) { 132 | this.icon = icon; 133 | } 134 | 135 | public String getKey() { 136 | return key; 137 | } 138 | 139 | public void setKey(String key) { 140 | this.key = key; 141 | } 142 | 143 | @Override 144 | public String getDatatype() { 145 | return ITEM_TYPE; 146 | } 147 | 148 | @Override 149 | public String getItemType() { 150 | return ITEM_TYPE; 151 | } 152 | 153 | } 154 | -------------------------------------------------------------------------------- /src/main/java/com/opera/link/apilib/items/SpeedDial.java: -------------------------------------------------------------------------------- 1 | package com.opera.link.apilib.items; 2 | 3 | import java.io.IOException; 4 | import java.util.HashMap; 5 | 6 | import org.json.JSONException; 7 | import org.json.JSONObject; 8 | 9 | import com.opera.link.apilib.Base64; 10 | 11 | 12 | public class SpeedDial extends Element { 13 | 14 | public static final String ITEM_TYPE = "speeddial"; 15 | 16 | @Override 17 | public String getDatatype() { 18 | return ITEM_TYPE; 19 | } 20 | 21 | public String title; 22 | public String uri; 23 | 24 | private int position; 25 | public int getPosition() { 26 | return position; 27 | } 28 | 29 | public byte[] thumbnail; 30 | 31 | protected SpeedDial() { 32 | 33 | } 34 | 35 | public SpeedDial(String uri, String title, int position) { 36 | this.uri = uri; 37 | this.title = title; 38 | this.position = position; 39 | } 40 | 41 | @Override 42 | void loadParameters(JSONObject json) throws JSONException { 43 | if (json.has(Element.TITLE_JSON_FIELD)) { 44 | title = json.getString(Element.TITLE_JSON_FIELD); 45 | } 46 | if (json.has(Element.URI_JSON_FIELD)) { 47 | uri = json.getString(Element.URI_JSON_FIELD); 48 | } 49 | if (json.has(Element.THUMBNAIL_JSON_FIELD)) { 50 | try { 51 | thumbnail = Base64.decode(json.getString(Element.THUMBNAIL_JSON_FIELD), Base64.NO_OPTIONS); 52 | } catch (IOException e) { 53 | e.printStackTrace(); 54 | thumbnail = null; 55 | } 56 | } 57 | } 58 | 59 | @Override 60 | public HashMap createParamsDict() { 61 | final String thumbnailString; 62 | if (thumbnail != null) { 63 | thumbnailString = Base64.encodeBytes(thumbnail); 64 | } else { 65 | thumbnailString = null; 66 | } 67 | 68 | HashMap params = new HashMap() { 69 | private static final long serialVersionUID = 1L; 70 | 71 | { 72 | put(Element.TITLE_JSON_FIELD, title); 73 | put(Element.URI_JSON_FIELD, uri); 74 | put(Element.THUMBNAIL_JSON_FIELD, thumbnailString); 75 | } 76 | }; 77 | return skipNullParams(params); 78 | } 79 | 80 | @Override 81 | public String getItemType() { 82 | return ITEM_TYPE; 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/com/opera/link/apilib/items/UrlFilter.java: -------------------------------------------------------------------------------- 1 | package com.opera.link.apilib.items; 2 | 3 | import java.util.HashMap; 4 | import org.json.JSONException; 5 | import org.json.JSONObject; 6 | 7 | 8 | 9 | public class UrlFilter extends Element { 10 | 11 | public static final String ITEM_TYPE = "urlfilter"; 12 | 13 | public String content; 14 | public String type; 15 | 16 | protected UrlFilter() { 17 | 18 | } 19 | 20 | public UrlFilter(String content, String type) { 21 | this(); 22 | this.content = content; 23 | this.type = type; 24 | } 25 | 26 | @Override 27 | void loadParameters(JSONObject json) throws JSONException { 28 | if (json.has(Element.CONTENT_JSON_FIELD)) { 29 | content = json.getString(Element.CONTENT_JSON_FIELD); 30 | } 31 | if (json.has(Element.TYPE_JSON_FIELD)) { 32 | type = json.getString(Element.TYPE_JSON_FIELD); 33 | } 34 | } 35 | 36 | @Override 37 | public HashMap createParamsDict() { 38 | HashMap params = new HashMap() { 39 | private static final long serialVersionUID = 1L; 40 | 41 | { 42 | put(Element.CONTENT_JSON_FIELD, content); 43 | put(Element.TYPE_JSON_FIELD, type); 44 | } 45 | }; 46 | return skipNullParams(params); 47 | } 48 | 49 | @Override 50 | public String getDatatype() { 51 | return ITEM_TYPE; 52 | } 53 | 54 | @Override 55 | public String getItemType() { 56 | return ITEM_TYPE; 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/test/java/com/opera/link/api/AppTest.java: -------------------------------------------------------------------------------- 1 | package com.opera.link.api; 2 | 3 | import junit.framework.Test; 4 | import junit.framework.TestCase; 5 | import junit.framework.TestSuite; 6 | 7 | /** 8 | * Unit test for simple App. 9 | */ 10 | public class AppTest 11 | extends TestCase 12 | { 13 | /** 14 | * Create the test case 15 | * 16 | * @param testName name of the test case 17 | */ 18 | public AppTest( String testName ) 19 | { 20 | super( testName ); 21 | } 22 | 23 | /** 24 | * @return the suite of tests being tested 25 | */ 26 | public static Test suite() 27 | { 28 | return new TestSuite( AppTest.class ); 29 | } 30 | 31 | /** 32 | * Rigourous Test :-) 33 | */ 34 | public void testApp() 35 | { 36 | assertTrue( true ); 37 | } 38 | } 39 | --------------------------------------------------------------------------------