/******************************************************************************* * Copyright (c) 2000-present Liferay, Inc. All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. * * Contributors: * Gregory Amerson - initial implementation and ongoing maintenance *******************************************************************************/ package com.liferay.ide.theme.core.util; import com.liferay.ide.theme.core.ThemeCore; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.List; import org.apache.commons.io.FileUtils; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceDelta; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.MultiStatus; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Status; import org.eclipse.osgi.util.NLS; import org.eclipse.wst.server.core.internal.Messages; import org.eclipse.wst.server.core.internal.ProgressUtil; /** * @author Gregory Amerson */ @SuppressWarnings( "restriction" ) public class BuildHelper { // size of the buffer private static final int BUFFER = 65536; // the buffer private static byte[] buf = new byte[BUFFER]; private static final IStatus[] EMPTY_STATUS = new IStatus[0]; private static final File defaultTempDir = ThemeCore.getDefault().getStateLocation().toFile(); private static final String TEMPFILE_PREFIX = ".tmp-safe-to-delete-"; //$NON-NLS-1$ private File tempDir; /** * Create a new PublishHelper. * * @param tempDirectory * a temporary directory to use during publishing, or <code>null</code> to use the default. If it does * not exist, the folder will be created */ public BuildHelper() { tempDir = defaultTempDir; if( !tempDir.exists() ) { tempDir.mkdirs(); } } /** * Copy a file from a to b. Closes the input stream after use. * * @param in * an input stream * @param to * a path to copy to. the directory must already exist * @param ts * timestamp * @throws CoreException * if anything goes wrong */ private void copyFile( InputStream in, IPath to, long ts, IFile mf ) throws CoreException { OutputStream out = null; File tempFile = null; File tempFileParentDir = null; try { File file = to.toFile(); // IDE-796 need to make sure temporary file is generated in same directory as file destination so that // file.renameTo() will never fail due to source/destination being on two different file systems if( file != null && file.getParentFile().exists() ) { tempFileParentDir = to.toFile().getParentFile(); } else { tempFileParentDir = tempDir; } tempFile = File.createTempFile( TEMPFILE_PREFIX, "." + to.getFileExtension(), tempFileParentDir ); //$NON-NLS-1$ out = new FileOutputStream( tempFile ); int avail = in.read( buf ); while( avail > 0 ) { out.write( buf, 0, avail ); avail = in.read( buf ); } out.close(); out = null; moveTempFile( tempFile, file ); if( ts != IResource.NULL_STAMP && ts != 0 ) file.setLastModified( ts ); } catch( CoreException e ) { throw e; } catch( Exception e ) { } finally { if( tempFile != null && tempFile.exists() ) tempFile.deleteOnExit(); try { if( in != null ) in.close(); } catch( Exception ex ) { // ignore } try { if( out != null ) out.close(); } catch( Exception ex ) { // ignore } } } /** * Utility method to recursively delete a directory. * * @param dir * a directory * @param monitor * a progress monitor, or <code>null</code> if progress reporting and cancellation are not desired * @return a possibly-empty array of error and warning status */ public static IStatus[] deleteDirectory( File dir, IProgressMonitor monitor ) { if( !dir.exists() || !dir.isDirectory() ) return new IStatus[] { new Status( IStatus.ERROR, ThemeCore.PLUGIN_ID, 0, NLS.bind( Messages.errorNotADirectory, dir.getAbsolutePath() ), null ) }; List<IStatus> status = new ArrayList<IStatus>( 2 ); try { File[] files = dir.listFiles(); int size = files.length; monitor = ProgressUtil.getMonitorFor( monitor ); monitor.beginTask( NLS.bind( Messages.deletingTask, new String[] { dir.getAbsolutePath() } ), size * 10 ); // cycle through files boolean deleteCurrent = true; for( int i = 0; i < size; i++ ) { File current = files[i]; if( current.isFile() ) { if( !current.delete() ) { status.add( new Status( IStatus.ERROR, ThemeCore.PLUGIN_ID, 0, NLS.bind( Messages.errorDeleting, files[i].getAbsolutePath() ), null ) ); deleteCurrent = false; } monitor.worked( 10 ); } else if( current.isDirectory() ) { monitor.subTask( NLS.bind( Messages.deletingTask, new String[] { current.getAbsolutePath() } ) ); IStatus[] stat = deleteDirectory( current, ProgressUtil.getSubMonitorFor( monitor, 10 ) ); if( stat != null && stat.length > 0 ) { deleteCurrent = false; addArrayToList( status, stat ); } } } if( deleteCurrent && !dir.delete() ) status.add( new Status( IStatus.ERROR, ThemeCore.PLUGIN_ID, 0, NLS.bind( Messages.errorDeleting, dir.getAbsolutePath() ), null ) ); monitor.done(); } catch( Exception e ) { ThemeCore.logError( "Error deleting directory " + dir.getAbsolutePath(), e ); //$NON-NLS-1$ status.add( new Status( IStatus.ERROR, ThemeCore.PLUGIN_ID, 0, e.getLocalizedMessage(), null ) ); } IStatus[] stat = new IStatus[status.size()]; status.toArray( stat ); return stat; } /** * Smart copy the given module resources to the given path. * * @param resources * an array of module resources * @param path * an external path to copy to * @param monitor * a progress monitor, or <code>null</code> if progress reporting and cancellation are not desired * @return a possibly-empty array of error and warning status */ public IStatus[] publishSmart( IResource[] resources, IPath path, IProgressMonitor monitor ) { return publishSmart( resources, path, null, monitor ); } /** * Smart copy the given module resources to the given path. * * @param resources * an array of module resources * @param path * an external path to copy to * @param ignore * an array of paths relative to path to ignore, i.e. not delete or copy over * @param monitor * a progress monitor, or <code>null</code> if progress reporting and cancellation are not desired * @return a possibly-empty array of error and warning status */ public IStatus[] publishSmart( IResource[] resources, IPath path, IPath[] ignore, IProgressMonitor monitor ) { if( resources == null ) return EMPTY_STATUS; monitor = ProgressUtil.getMonitorFor( monitor ); List<IStatus> status = new ArrayList<IStatus>( 2 ); File toDir = path.toFile(); int fromSize = resources.length; String[] fromFileNames = new String[fromSize]; for( int i = 0; i < fromSize; i++ ) fromFileNames[i] = resources[i].getName(); List<String> ignoreFileNames = new ArrayList<String>(); if( ignore != null ) { for( int i = 0; i < ignore.length; i++ ) { if( ignore[i].segmentCount() == 1 ) { ignoreFileNames.add( ignore[i].toOSString() ); } } } // cache files and file names for performance File[] toFiles = null; String[] toFileNames = null; boolean foundExistingDir = false; if( toDir.exists() ) { if( toDir.isDirectory() ) { foundExistingDir = true; toFiles = toDir.listFiles(); int toSize = toFiles.length; toFileNames = new String[toSize]; // check if this exact file exists in the new directory for( int i = 0; i < toSize; i++ ) { toFileNames[i] = toFiles[i].getName(); boolean isDir = toFiles[i].isDirectory(); boolean found = false; for( int j = 0; j < fromSize; j++ ) { if( toFileNames[i].equals( fromFileNames[j] ) && isDir == resources[j] instanceof IFolder ) { found = true; break; } } // delete file if it can't be found or isn't the correct type if( !found ) { boolean delete = true; // if should be preserved, don't delete and don't try to copy for( String preserveFileName : ignoreFileNames ) { if( toFileNames[i].equals( preserveFileName ) ) { delete = false; break; } } if( delete ) { if( isDir ) { IStatus[] stat = deleteDirectory( toFiles[i], null ); addArrayToList( status, stat ); } else { if( !toFiles[i].delete() ) status.add( new Status( IStatus.ERROR, ThemeCore.PLUGIN_ID, 0, NLS.bind( Messages.errorDeleting, toFiles[i].getAbsolutePath() ), null ) ); } } toFiles[i] = null; toFileNames[i] = null; } } } else { // if (toDir.isFile()) if( !toDir.delete() ) { status.add( new Status( IStatus.ERROR, ThemeCore.PLUGIN_ID, 0, NLS.bind( Messages.errorDeleting, toDir.getAbsolutePath() ), null ) ); IStatus[] stat = new IStatus[status.size()]; status.toArray( stat ); return stat; } } } if( !foundExistingDir && !toDir.mkdirs() ) { status.add( new Status( IStatus.ERROR, ThemeCore.PLUGIN_ID, 0, NLS.bind( Messages.errorMkdir, toDir.getAbsolutePath() ), null ) ); IStatus[] stat = new IStatus[status.size()]; status.toArray( stat ); return stat; } if( monitor.isCanceled() ) return new IStatus[] { Status.CANCEL_STATUS }; monitor.worked( 50 ); // cycle through files and only copy when it doesn't exist // or is newer if( toFiles == null ) { toFiles = toDir.listFiles(); if( toFiles == null ) toFiles = new File[0]; } int toSize = toFiles.length; int dw = 0; if( toSize > 0 ) dw = 500 / toSize; // cache file names and last modified dates for performance if( toFileNames == null ) toFileNames = new String[toSize]; long[] toFileMod = new long[toSize]; for( int i = 0; i < toSize; i++ ) { if( toFiles[i] != null ) { if( toFileNames[i] != null ) toFileNames[i] = toFiles[i].getName(); toFileMod[i] = toFiles[i].lastModified(); } } for( int i = 0; i < fromSize; i++ ) { IResource current = resources[i]; String name = fromFileNames[i]; boolean currentIsDir = current instanceof IFolder; if( !currentIsDir ) { // check if this is a new or newer file boolean copy = true; IFile mf = (IFile) current; long mod = -1; IFile file = (IFile) mf.getAdapter( IFile.class ); if( file != null ) { mod = file.getLocalTimeStamp(); } else { File file2 = (File) mf.getAdapter( File.class ); mod = file2.lastModified(); } for( int j = 0; j < toSize; j++ ) { if( name.equals( toFileNames[j] ) && mod == toFileMod[j] ) { copy = false; break; } } if( copy ) { try { copyFile( mf, path.append( name ) ); } catch( CoreException ce ) { status.add( ce.getStatus() ); } } monitor.worked( dw ); } else { // if (currentIsDir) { IFolder folder = (IFolder) current; IResource[] children = null; try { children = folder.members(); } catch( CoreException e ) { e.printStackTrace(); } // build array of ignored Paths that apply to this folder IPath[] ignoreChildren = null; if( ignore != null ) { List<IPath> ignoreChildPaths = new ArrayList<IPath>(); for( int j = 0; j < ignore.length; j++ ) { IPath preservePath = ignore[j]; if( preservePath.segment( 0 ).equals( name ) ) { ignoreChildPaths.add( preservePath.removeFirstSegments( 1 ) ); } } if( ignoreChildPaths.size() > 0 ) ignoreChildren = ignoreChildPaths.toArray( new Path[ignoreChildPaths.size()] ); } monitor.subTask( NLS.bind( Messages.copyingTask, new String[] { name, name } ) ); IStatus[] stat = publishSmart( children, path.append( name ), ignoreChildren, ProgressUtil.getSubMonitorFor( monitor, dw ) ); addArrayToList( status, stat ); } } if( monitor.isCanceled() ) return new IStatus[] { Status.CANCEL_STATUS }; monitor.worked( 500 - dw * toSize ); monitor.done(); IStatus[] stat = new IStatus[status.size()]; status.toArray( stat ); return stat; } /** * Handle a delta publish. * * @param delta * a module resource delta * @param path * the path to publish to * @param monitor * a progress monitor, or <code>null</code> if progress reporting and cancellation are not desired * @return a possibly-empty array of error and warning status */ public IStatus[] publishDelta( IResourceDelta[] delta, IPath path, IPath[] restorePaths, IProgressMonitor monitor ) { if( delta == null ) return EMPTY_STATUS; monitor = ProgressUtil.getMonitorFor( monitor ); List<IStatus> status = new ArrayList<IStatus>( 2 ); int size2 = delta.length; for( int i = 0; i < size2; i++ ) { IStatus[] stat = publishDelta( delta[i], path, restorePaths, monitor ); addArrayToList( status, stat ); } IStatus[] stat = new IStatus[status.size()]; status.toArray( stat ); return stat; } /** * Handle a delta publish. * * @param delta * a module resource delta * @param path * the path to publish to * @param monitor * a progress monitor, or <code>null</code> if progress reporting and cancellation are not desired * @return a possibly-empty array of error and warning status */ public IStatus[] publishDelta( IResourceDelta delta, IPath path, IPath[] restorePaths, IProgressMonitor monitor ) { List<IStatus> status = new ArrayList<IStatus>( 2 ); IResource resource = delta.getResource(); int kind2 = delta.getKind(); if( resource instanceof IFile ) { IFile file = (IFile) resource; try { if( kind2 == IResourceDelta.REMOVED ) { deleteFile( path, file, restorePaths ); } else { IPath diffsRelativePath = getDiffsRelativePath(file.getProjectRelativePath()); if (diffsRelativePath != null) { IPath path2 = path.append( diffsRelativePath ); File f = path2.toFile().getParentFile(); if( !f.exists() ) { f.mkdirs(); } copyFile( file, path2 ); } } } catch( CoreException ce ) { status.add( ce.getStatus() ); } IStatus[] stat = new IStatus[status.size()]; status.toArray( stat ); return stat; } if( kind2 == IResourceDelta.ADDED ) { // find relative path from _diffs and append that to path. IPath diffsPath = resource.getProjectRelativePath(); IPath diffsRelativePath = getDiffsRelativePath( diffsPath ); if (diffsRelativePath != null) { IPath path2 = path.append(diffsRelativePath); // IPath path2 = path.append( resource.getProjectRelativePath() ).append( resource.getName() ); File file = path2.toFile(); if( !file.exists() && !file.mkdirs() ) { status.add( new Status( IStatus.ERROR, ThemeCore.PLUGIN_ID, 0, NLS.bind( Messages.errorMkdir, path2 ), null ) ); IStatus[] stat = new IStatus[status.size()]; status.toArray( stat ); return stat; } } } IResourceDelta[] childDeltas = delta.getAffectedChildren(); int size = childDeltas.length; for( int i = 0; i < size; i++ ) { IStatus[] stat = publishDelta( childDeltas[i], path, restorePaths, monitor ); addArrayToList( status, stat ); } if( kind2 == IResourceDelta.REMOVED ) { IPath diffsRelativePath = getDiffsRelativePath( resource.getProjectRelativePath() ); if (diffsRelativePath != null) { IPath path2 = path.append(diffsRelativePath); //IPath path2 = path.append( resource.getProjectRelativePath() ).append( resource.getName() ); File file = path2.toFile(); if( file.exists() && !file.delete() ) { status.add( new Status( IStatus.ERROR, ThemeCore.PLUGIN_ID, 0, NLS.bind( Messages.errorDeleting, path2 ), null ) ); } } } IStatus[] stat = new IStatus[status.size()]; status.toArray( stat ); return stat; } private static IPath getDiffsRelativePath(IPath diffsPath) { IPath diffsRelativePath = null; for (int i = 0; i < diffsPath.segmentCount(); i++) { if ("_diffs".equals(diffsPath.segment( i ))) //$NON-NLS-1$ { diffsRelativePath = diffsPath.removeFirstSegments( i + 1 ); break; } } return diffsRelativePath; } private static void deleteFile( IPath path, IFile file, IPath[] restorePaths ) throws CoreException { IPath diffsPath = file.getProjectRelativePath(); IPath diffsRelativePath = getDiffsRelativePath( diffsPath ); if (diffsRelativePath != null) { // IPath path2 = path.append( file.getProjectRelativePath() ).append( file.getName() ); IPath path2 = path.append(diffsRelativePath); // restore this file from the first restorePaths that matches boolean restored = false; for (IPath restorePath : restorePaths) { final File restoreFile = restorePath.append( diffsRelativePath ).toFile(); if ( restoreFile.exists() ) { try { FileUtils.copyFile( restoreFile, path2.toFile() ); restored = true; break; } catch( IOException e ) { throw new CoreException( new Status( IStatus.ERROR, ThemeCore.PLUGIN_ID, 0, NLS.bind( "Error restoring theme file.", path2 ), null ) ); //$NON-NLS-1$ } } } if (!restored) { if( path2.toFile().exists() && !path2.toFile().delete() ) { throw new CoreException( new Status( IStatus.ERROR, ThemeCore.PLUGIN_ID, 0, NLS.bind( Messages.errorDeleting, path2 ), null ) ); } } } } private void copyFile( IFile mf, IPath path ) throws CoreException { if( !isCopyFile( mf, path ) ) { return; } IFile file = (IFile) mf.getAdapter( IFile.class ); if( file != null ) copyFile( file.getContents(), path, file.getLocalTimeStamp(), mf ); else { File file2 = (File) mf.getAdapter( File.class ); InputStream in = null; try { in = new FileInputStream( file2 ); } catch( IOException e ) { throw new CoreException( new Status( IStatus.ERROR, ThemeCore.PLUGIN_ID, 0, NLS.bind( Messages.errorReading, file2.getAbsolutePath() ), e ) ); } copyFile( in, path, file2.lastModified(), mf ); } } /** * Returns <code>true<code/> if the module file should be copied to the destination, <code>false</codre> otherwise. * * @param moduleFile * the module file * @param toPath * destination. * @return <code>true<code/>, if the module file should be copied */ protected boolean isCopyFile( IFile moduleFile, IPath toPath ) { return true; } /** * Publish the given module resources to the given path. * * @param resources * an array of module resources * @param path * a path to publish to * @param monitor * a progress monitor, or <code>null</code> if progress reporting and cancellation are not desired * @return a possibly-empty array of error and warning status */ public IStatus[] publishFull( IResource[] resources, IPath path, IProgressMonitor monitor ) { if( resources == null ) return EMPTY_STATUS; monitor = ProgressUtil.getMonitorFor( monitor ); List<IStatus> status = new ArrayList<IStatus>( 2 ); int size = resources.length; for( int i = 0; i < size; i++ ) { IStatus[] stat = copy( resources[i], path, monitor ); addArrayToList( status, stat ); if( monitor.isCanceled() ) { break; } } IStatus[] stat = new IStatus[status.size()]; status.toArray( stat ); return stat; } private IStatus[] copy( IResource resource, IPath path, IProgressMonitor monitor ) { if( monitor != null && monitor.isCanceled() ) { return new IStatus[0]; } List<IStatus> status = new ArrayList<IStatus>( 2 ); if( resource instanceof IFolder ) { IFolder folder = (IFolder) resource; IStatus[] stat; try { stat = publishFull( folder.members(), path, monitor ); addArrayToList( status, stat ); } catch( CoreException e ) { e.printStackTrace(); } } else { IFile mf = (IFile) resource; IPath diffsRelativePath = getDiffsRelativePath( mf.getProjectRelativePath()); if (diffsRelativePath != null) { // path = path.append( mf.getProjectRelativePath() ).append( name ); path = path.append(diffsRelativePath); File f = path.toFile().getParentFile(); if ( f.exists() ) { try { copyFile( mf, path ); } catch( CoreException ce ) { status.add( ce.getStatus() ); } } else { // Create the parent directory. if ( f.mkdirs() ) { try { copyFile( mf, path ); } catch( CoreException ce ) { status.add( ce.getStatus() ); } } else { status.add(new Status(IStatus.ERROR, ThemeCore.PLUGIN_ID, 0, NLS.bind(Messages.errorMkdir, f.getAbsolutePath()), null)); } } } } IStatus[] stat = new IStatus[status.size()]; status.toArray( stat ); return stat; } /** * Accepts an IModuleResource array which is expected to contain a single IModuleFile resource and copies it to the * specified path, which should include the name of the file to write. If the array contains more than a single * resource or the resource is not an IModuleFile resource, the file is not created. Currently no error is returned, * but error handling is recommended since that is expected to change in the future. * * @param resources * an array containing a single IModuleFile resource * @param path * the path, including file name, where the file should be created * @param monitor * a progress monitor, or <code>null</code> if progress reporting and cancellation are not desired * @return a possibly-empty array of error and warning status */ public IStatus[] publishToPath( IResource[] resources, IPath path, IProgressMonitor monitor ) { if( resources == null || resources.length == 0 ) { // should also check if resources consists of all empty directories File file = path.toFile(); if( file.exists() ) file.delete(); return EMPTY_STATUS; } monitor = ProgressUtil.getMonitorFor( monitor ); if( resources.length == 1 && resources[0] instanceof IFile ) { try { copyFile( (IFile) resources[0], path ); } catch( CoreException e ) { return new IStatus[] { e.getStatus() }; } } return EMPTY_STATUS; } /** * Utility method to move a temp file into position by deleting the original and swapping in a new copy. * * @param tempFile * @param file * @throws CoreException */ private void moveTempFile( File tempFile, File file ) throws CoreException { if( file.exists() ) { if( !safeDelete( file, 2 ) ) { // attempt to rewrite an existing file with the tempFile contents if // the existing file can't be deleted to permit the move try { InputStream in = new FileInputStream( tempFile ); IStatus status = copyFile( in, file.getPath() ); if( !status.isOK() ) { MultiStatus status2 = new MultiStatus( ThemeCore.PLUGIN_ID, 0, NLS.bind( Messages.errorDeleting, file.toString() ), null ); status2.add( status ); throw new CoreException( status2 ); } return; } catch( FileNotFoundException e ) { // shouldn't occur } finally { tempFile.delete(); } /* * if (!safeDelete(file, 8)) { tempFile.delete(); throw new CoreException(new Status(IStatus.ERROR, * ThemeCore.PLUGIN_ID, 0, NLS.bind(Messages.errorDeleting, file.toString()), null)); } */ } } if( !safeRename( tempFile, file, 10 ) ) throw new CoreException( new Status( IStatus.ERROR, ThemeCore.PLUGIN_ID, 0, NLS.bind( Messages.errorRename, tempFile.toString() ), null ) ); } /** * Copy a file from a to b. Closes the input stream after use. * * @param in * an InputStream * @param to * the file to copy to * @return a status */ private IStatus copyFile( InputStream in, String to ) { OutputStream out = null; try { out = new FileOutputStream( to ); int avail = in.read( buf ); while( avail > 0 ) { out.write( buf, 0, avail ); avail = in.read( buf ); } return Status.OK_STATUS; } catch( Exception e ) { ThemeCore.logError( "Error copying file", e ); //$NON-NLS-1$ return new Status( IStatus.ERROR, ThemeCore.PLUGIN_ID, 0, NLS.bind( Messages.errorCopyingFile, new String[] { to, e.getLocalizedMessage() } ), e ); } finally { try { if( in != null ) in.close(); } catch( Exception ex ) { // ignore } try { if( out != null ) out.close(); } catch( Exception ex ) { // ignore } } } /** * Safe delete. Tries to delete multiple times before giving up. * * @param f * @return <code>true</code> if it succeeds, <code>false</code> otherwise */ private static boolean safeDelete( File f, int retrys ) { int count = 0; while( count < retrys ) { if( f.delete() ) { return true; } count++; // delay if we are going to try again if( count < retrys ) { try { Thread.sleep( 100 ); } catch( Exception e ) { // ignore } } } return false; } /** * Safe rename. Will try multiple times before giving up. * * @param from * @param to * @param retrys * number of times to retry * @return <code>true</code> if it succeeds, <code>false</code> otherwise */ private static boolean safeRename( File from, File to, int retrys ) { // make sure parent dir exists File dir = to.getParentFile(); if( dir != null && !dir.exists() ) dir.mkdirs(); int count = 0; while( count < retrys ) { if( from.renameTo( to ) ) return true; count++; // delay if we are going to try again if( count < retrys ) { try { Thread.sleep( 100 ); } catch( Exception e ) { // ignore } } } return false; } private static void addArrayToList( List<IStatus> list, IStatus[] a ) { if( list == null || a == null || a.length == 0 ) return; int size = a.length; for( int i = 0; i < size; i++ ) list.add( a[i] ); } }