1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/src/share/jaxws_classes/com/sun/tools/internal/ws/wscompile/WsimportTool.java Wed Apr 27 01:27:09 2016 +0800 1.3 @@ -0,0 +1,586 @@ 1.4 +/* 1.5 + * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved. 1.6 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 1.7 + * 1.8 + * This code is free software; you can redistribute it and/or modify it 1.9 + * under the terms of the GNU General Public License version 2 only, as 1.10 + * published by the Free Software Foundation. Oracle designates this 1.11 + * particular file as subject to the "Classpath" exception as provided 1.12 + * by Oracle in the LICENSE file that accompanied this code. 1.13 + * 1.14 + * This code is distributed in the hope that it will be useful, but WITHOUT 1.15 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 1.16 + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 1.17 + * version 2 for more details (a copy is included in the LICENSE file that 1.18 + * accompanied this code). 1.19 + * 1.20 + * You should have received a copy of the GNU General Public License version 1.21 + * 2 along with this work; if not, write to the Free Software Foundation, 1.22 + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 1.23 + * 1.24 + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 1.25 + * or visit www.oracle.com if you need additional information or have any 1.26 + * questions. 1.27 + */ 1.28 + 1.29 +package com.sun.tools.internal.ws.wscompile; 1.30 + 1.31 +import com.sun.codemodel.internal.CodeWriter; 1.32 +import com.sun.codemodel.internal.writer.ProgressCodeWriter; 1.33 +import com.sun.istack.internal.tools.DefaultAuthenticator; 1.34 +import com.sun.tools.internal.ws.ToolVersion; 1.35 +import com.sun.tools.internal.ws.api.TJavaGeneratorExtension; 1.36 +import com.sun.tools.internal.ws.processor.generator.CustomExceptionGenerator; 1.37 +import com.sun.tools.internal.ws.processor.generator.GeneratorBase; 1.38 +import com.sun.tools.internal.ws.processor.generator.SeiGenerator; 1.39 +import com.sun.tools.internal.ws.processor.generator.ServiceGenerator; 1.40 +import com.sun.tools.internal.ws.processor.generator.JwsImplGenerator; 1.41 +import com.sun.tools.internal.ws.processor.model.Model; 1.42 +import com.sun.tools.internal.ws.processor.modeler.wsdl.ConsoleErrorReporter; 1.43 +import com.sun.tools.internal.ws.processor.modeler.wsdl.WSDLModeler; 1.44 +import com.sun.tools.internal.ws.processor.util.DirectoryUtil; 1.45 +import com.sun.tools.internal.ws.resources.WscompileMessages; 1.46 +import com.sun.tools.internal.ws.resources.WsdlMessages; 1.47 +import com.sun.tools.internal.ws.util.WSDLFetcher; 1.48 +import com.sun.tools.internal.ws.wsdl.parser.MetadataFinder; 1.49 +import com.sun.tools.internal.ws.wsdl.parser.WSDLInternalizationLogic; 1.50 +import com.sun.tools.internal.xjc.util.NullStream; 1.51 +import com.sun.xml.internal.ws.api.server.Container; 1.52 +import com.sun.xml.internal.ws.util.ServiceFinder; 1.53 +import com.sun.istack.internal.tools.ParallelWorldClassLoader; 1.54 +import org.xml.sax.EntityResolver; 1.55 +import org.xml.sax.SAXParseException; 1.56 + 1.57 +import javax.xml.bind.JAXBPermission; 1.58 +import javax.xml.stream.*; 1.59 +import javax.xml.ws.EndpointContext; 1.60 +import java.io.*; 1.61 +import java.util.*; 1.62 +import java.text.MessageFormat; 1.63 +import java.util.jar.JarEntry; 1.64 +import java.util.jar.JarOutputStream; 1.65 +import org.xml.sax.Locator; 1.66 +import org.xml.sax.SAXException; 1.67 + 1.68 +/** 1.69 + * @author Vivek Pandey 1.70 + */ 1.71 +public class WsimportTool { 1.72 + private static final String WSIMPORT = "wsimport"; 1.73 + private final PrintStream out; 1.74 + private final Container container; 1.75 + 1.76 + /** 1.77 + * Wsimport specific options 1.78 + */ 1.79 + protected WsimportOptions options = new WsimportOptions(); 1.80 + 1.81 + public WsimportTool(OutputStream out) { 1.82 + this(out, null); 1.83 + } 1.84 + 1.85 + public WsimportTool(OutputStream logStream, Container container) { 1.86 + this.out = (logStream instanceof PrintStream)?(PrintStream)logStream:new PrintStream(logStream); 1.87 + this.container = container; 1.88 + } 1.89 + 1.90 + protected class Listener extends WsimportListener { 1.91 + ConsoleErrorReporter cer = new ConsoleErrorReporter(out == null ? new PrintStream(new NullStream()) : out); 1.92 + 1.93 + @Override 1.94 + public void generatedFile(String fileName) { 1.95 + message(fileName); 1.96 + } 1.97 + 1.98 + @Override 1.99 + public void message(String msg) { 1.100 + out.println(msg); 1.101 + } 1.102 + 1.103 + @Override 1.104 + public void error(SAXParseException exception) { 1.105 + cer.error(exception); 1.106 + } 1.107 + 1.108 + @Override 1.109 + public void fatalError(SAXParseException exception) { 1.110 + cer.fatalError(exception); 1.111 + } 1.112 + 1.113 + @Override 1.114 + public void warning(SAXParseException exception) { 1.115 + cer.warning(exception); 1.116 + } 1.117 + 1.118 + @Override 1.119 + public void debug(SAXParseException exception) { 1.120 + cer.debug(exception); 1.121 + } 1.122 + 1.123 + @Override 1.124 + public void info(SAXParseException exception) { 1.125 + cer.info(exception); 1.126 + } 1.127 + 1.128 + public void enableDebugging(){ 1.129 + cer.enableDebugging(); 1.130 + } 1.131 + } 1.132 + 1.133 + protected class Receiver extends ErrorReceiverFilter { 1.134 + 1.135 + private Listener listener; 1.136 + 1.137 + public Receiver(Listener listener) { 1.138 + super(listener); 1.139 + this.listener = listener; 1.140 + } 1.141 + 1.142 + @Override 1.143 + public void info(SAXParseException exception) { 1.144 + if (options.verbose) 1.145 + super.info(exception); 1.146 + } 1.147 + 1.148 + @Override 1.149 + public void warning(SAXParseException exception) { 1.150 + if (!options.quiet) 1.151 + super.warning(exception); 1.152 + } 1.153 + 1.154 + @Override 1.155 + public void pollAbort() throws AbortException { 1.156 + if (listener.isCanceled()) 1.157 + throw new AbortException(); 1.158 + } 1.159 + 1.160 + @Override 1.161 + public void debug(SAXParseException exception){ 1.162 + if(options.debugMode){ 1.163 + listener.debug(exception); 1.164 + } 1.165 + } 1.166 + } 1.167 + 1.168 + public boolean run(String[] args) { 1.169 + Listener listener = new Listener(); 1.170 + Receiver receiver = new Receiver(listener); 1.171 + return run(args, listener, receiver); 1.172 + } 1.173 + 1.174 + protected boolean run(String[] args, Listener listener, 1.175 + Receiver receiver) { 1.176 + for (String arg : args) { 1.177 + if (arg.equals("-version")) { 1.178 + listener.message( 1.179 + WscompileMessages.WSIMPORT_VERSION(ToolVersion.VERSION.MAJOR_VERSION)); 1.180 + return true; 1.181 + } 1.182 + if (arg.equals("-fullversion")) { 1.183 + listener.message( 1.184 + WscompileMessages.WSIMPORT_FULLVERSION(ToolVersion.VERSION.toString())); 1.185 + return true; 1.186 + } 1.187 + } 1.188 + 1.189 + try { 1.190 + parseArguments(args, listener, receiver); 1.191 + 1.192 + try { 1.193 + Model wsdlModel = buildWsdlModel(listener, receiver); 1.194 + if (wsdlModel == null) 1.195 + return false; 1.196 + 1.197 + if (!generateCode(listener, receiver, wsdlModel, true)) 1.198 + return false; 1.199 + 1.200 + /* Not so fast! 1.201 + } catch(AbortException e){ 1.202 + //error might have been reported 1.203 + * 1.204 + */ 1.205 + }catch (IOException e) { 1.206 + receiver.error(e); 1.207 + return false; 1.208 + }catch (XMLStreamException e) { 1.209 + receiver.error(e); 1.210 + return false; 1.211 + } 1.212 + if (!options.nocompile){ 1.213 + if(!compileGeneratedClasses(receiver, listener)){ 1.214 + listener.message(WscompileMessages.WSCOMPILE_COMPILATION_FAILED()); 1.215 + return false; 1.216 + } 1.217 + } 1.218 + try { 1.219 + if (options.clientjar != null) { 1.220 + //add all the generated class files to the list of generated files 1.221 + addClassesToGeneratedFiles(); 1.222 + jarArtifacts(listener); 1.223 + 1.224 + } 1.225 + } catch (IOException e) { 1.226 + receiver.error(e); 1.227 + return false; 1.228 + } 1.229 + 1.230 + } catch (Options.WeAreDone done) { 1.231 + usage(done.getOptions()); 1.232 + } catch (BadCommandLineException e) { 1.233 + if (e.getMessage() != null) { 1.234 + System.out.println(e.getMessage()); 1.235 + System.out.println(); 1.236 + } 1.237 + usage(e.getOptions()); 1.238 + return false; 1.239 + } finally{ 1.240 + deleteGeneratedFiles(); 1.241 + if (!options.disableAuthenticator) { 1.242 + DefaultAuthenticator.reset(); 1.243 + } 1.244 + } 1.245 + if(receiver.hadError()) { 1.246 + return false; 1.247 + } 1.248 + return true; 1.249 + } 1.250 + 1.251 + private void deleteGeneratedFiles() { 1.252 + Set<File> trackedRootPackages = new HashSet<File>(); 1.253 + 1.254 + if (options.clientjar != null) { 1.255 + //remove all non-java artifacts as they will packaged in jar. 1.256 + Iterable<File> generatedFiles = options.getGeneratedFiles(); 1.257 + synchronized (generatedFiles) { 1.258 + for (File file : generatedFiles) { 1.259 + if (!file.getName().endsWith(".java")) { 1.260 + boolean deleted = file.delete(); 1.261 + if (options.verbose && !deleted) { 1.262 + System.out.println(MessageFormat.format("{0} could not be deleted.", file)); 1.263 + } 1.264 + trackedRootPackages.add(file.getParentFile()); 1.265 + } 1.266 + } 1.267 + } 1.268 + //remove empty package dirs 1.269 + for(File pkg:trackedRootPackages) { 1.270 + 1.271 + while(pkg.list() != null && pkg.list().length ==0 && !pkg.equals(options.destDir)) { 1.272 + File parentPkg = pkg.getParentFile(); 1.273 + boolean deleted = pkg.delete(); 1.274 + if (options.verbose && !deleted) { 1.275 + System.out.println(MessageFormat.format("{0} could not be deleted.", pkg)); 1.276 + } 1.277 + pkg = parentPkg; 1.278 + } 1.279 + } 1.280 + } 1.281 + if(!options.keep) { 1.282 + options.removeGeneratedFiles(); 1.283 + } 1.284 + } 1.285 + 1.286 + private void addClassesToGeneratedFiles() throws IOException { 1.287 + Iterable<File> generatedFiles = options.getGeneratedFiles(); 1.288 + final List<File> trackedClassFiles = new ArrayList<File>(); 1.289 + for(File f: generatedFiles) { 1.290 + if(f.getName().endsWith(".java")) { 1.291 + String relativeDir = DirectoryUtil.getRelativePathfromCommonBase(f.getParentFile(),options.sourceDir); 1.292 + final String className = f.getName().substring(0,f.getName().indexOf(".java")); 1.293 + File classDir = new File(options.destDir,relativeDir); 1.294 + if(classDir.exists()) { 1.295 + classDir.listFiles(new FilenameFilter() { 1.296 + @Override 1.297 + public boolean accept(File dir, String name) { 1.298 + if(name.equals(className+".class") || (name.startsWith(className+"$") && name.endsWith(".class"))) { 1.299 + trackedClassFiles.add(new File(dir,name)); 1.300 + return true; 1.301 + } 1.302 + return false; 1.303 + } 1.304 + }); 1.305 + } 1.306 + } 1.307 + } 1.308 + for(File f: trackedClassFiles) { 1.309 + options.addGeneratedFile(f); 1.310 + } 1.311 + } 1.312 + 1.313 + private void jarArtifacts(WsimportListener listener) throws IOException { 1.314 + File zipFile = new File(options.clientjar); 1.315 + if(!zipFile.isAbsolute()) { 1.316 + zipFile = new File(options.destDir, options.clientjar); 1.317 + } 1.318 + 1.319 + FileOutputStream fos; 1.320 + if (!options.quiet) { 1.321 + listener.message(WscompileMessages.WSIMPORT_ARCHIVING_ARTIFACTS(zipFile)); 1.322 + } 1.323 + 1.324 + BufferedInputStream bis = null; 1.325 + FileInputStream fi = null; 1.326 + fos = new FileOutputStream(zipFile); 1.327 + JarOutputStream jos = new JarOutputStream(fos); 1.328 + try { 1.329 + String base = options.destDir.getCanonicalPath(); 1.330 + for(File f: options.getGeneratedFiles()) { 1.331 + //exclude packaging the java files in the jar 1.332 + if(f.getName().endsWith(".java")) { 1.333 + continue; 1.334 + } 1.335 + if(options.verbose) { 1.336 + listener.message(WscompileMessages.WSIMPORT_ARCHIVE_ARTIFACT(f, options.clientjar)); 1.337 + } 1.338 + String entry = f.getCanonicalPath().substring(base.length()+1).replace(File.separatorChar, '/'); 1.339 + fi = new FileInputStream(f); 1.340 + bis = new BufferedInputStream(fi); 1.341 + JarEntry jarEntry = new JarEntry(entry); 1.342 + jos.putNextEntry(jarEntry); 1.343 + int bytesRead; 1.344 + byte[] buffer = new byte[1024]; 1.345 + while ((bytesRead = bis.read(buffer)) != -1) { 1.346 + jos.write(buffer, 0, bytesRead); 1.347 + } 1.348 + } 1.349 + } finally { 1.350 + try { 1.351 + if (bis != null) { 1.352 + bis.close(); 1.353 + } 1.354 + } finally { 1.355 + if (jos != null) { 1.356 + jos.close(); 1.357 + } 1.358 + if (fi != null) { 1.359 + fi.close(); 1.360 + } 1.361 + } 1.362 + } 1.363 + } 1.364 + 1.365 + protected void parseArguments(String[] args, Listener listener, 1.366 + Receiver receiver) throws BadCommandLineException { 1.367 + options.parseArguments(args); 1.368 + options.validate(); 1.369 + if (options.debugMode) 1.370 + listener.enableDebugging(); 1.371 + options.parseBindings(receiver); 1.372 + } 1.373 + 1.374 + protected Model buildWsdlModel(Listener listener, final Receiver receiver) 1.375 + throws BadCommandLineException, XMLStreamException, IOException { 1.376 + //set auth info 1.377 + //if(options.authFile != null) 1.378 + if (!options.disableAuthenticator) { 1.379 + class AuthListener implements DefaultAuthenticator.Receiver { 1.380 + 1.381 + private final boolean isFatal; 1.382 + 1.383 + AuthListener(boolean isFatal) { 1.384 + this.isFatal = isFatal; 1.385 + } 1.386 + 1.387 + @Override 1.388 + public void onParsingError(String text, Locator loc) { 1.389 + error(new SAXParseException(WscompileMessages.WSIMPORT_ILLEGAL_AUTH_INFO(text), loc)); 1.390 + } 1.391 + 1.392 + @Override 1.393 + public void onError(Exception e, Locator loc) { 1.394 + if (e instanceof FileNotFoundException) { 1.395 + error(new SAXParseException(WscompileMessages.WSIMPORT_AUTH_FILE_NOT_FOUND( 1.396 + loc.getSystemId(), WsimportOptions.defaultAuthfile), null)); 1.397 + } else { 1.398 + error(new SAXParseException(WscompileMessages.WSIMPORT_FAILED_TO_PARSE(loc.getSystemId(),e.getMessage()), loc)); 1.399 + } 1.400 + } 1.401 + 1.402 + private void error(SAXParseException e) { 1.403 + if (isFatal) { 1.404 + receiver.error(e); 1.405 + } else { 1.406 + receiver.debug(e); 1.407 + } 1.408 + } 1.409 + } 1.410 + 1.411 + DefaultAuthenticator da = DefaultAuthenticator.getAuthenticator(); 1.412 + if (options.proxyAuth != null) { 1.413 + da.setProxyAuth(options.proxyAuth); 1.414 + } 1.415 + if (options.authFile != null) { 1.416 + da.setAuth(options.authFile, new AuthListener(true)); 1.417 + } else { 1.418 + da.setAuth(new File(WsimportOptions.defaultAuthfile), new AuthListener(false)); 1.419 + } 1.420 + } 1.421 + 1.422 + if (!options.quiet) { 1.423 + listener.message(WscompileMessages.WSIMPORT_PARSING_WSDL()); 1.424 + } 1.425 + 1.426 + MetadataFinder forest = new MetadataFinder(new WSDLInternalizationLogic(), options, receiver); 1.427 + forest.parseWSDL(); 1.428 + if (forest.isMexMetadata) 1.429 + receiver.reset(); 1.430 + 1.431 + WSDLModeler wsdlModeler = new WSDLModeler(options, receiver,forest); 1.432 + Model wsdlModel = wsdlModeler.buildModel(); 1.433 + if (wsdlModel == null) { 1.434 + listener.message(WsdlMessages.PARSING_PARSE_FAILED()); 1.435 + } 1.436 + 1.437 + if(options.clientjar != null) { 1.438 + if( !options.quiet ) 1.439 + listener.message(WscompileMessages.WSIMPORT_FETCHING_METADATA()); 1.440 + options.wsdlLocation = new WSDLFetcher(options,listener).fetchWsdls(forest); 1.441 + } 1.442 + 1.443 + return wsdlModel; 1.444 + } 1.445 + 1.446 + protected boolean generateCode(Listener listener, Receiver receiver, 1.447 + Model wsdlModel, boolean generateService) 1.448 + throws IOException { 1.449 + //generated code 1.450 + if( !options.quiet ) 1.451 + listener.message(WscompileMessages.WSIMPORT_GENERATING_CODE()); 1.452 + 1.453 + TJavaGeneratorExtension[] genExtn = ServiceFinder.find(TJavaGeneratorExtension.class).toArray(); 1.454 + CustomExceptionGenerator.generate(wsdlModel, options, receiver); 1.455 + SeiGenerator.generate(wsdlModel, options, receiver, genExtn); 1.456 + if(receiver.hadError()){ 1.457 + throw new AbortException(); 1.458 + } 1.459 + if (generateService) 1.460 + { 1.461 + ServiceGenerator.generate(wsdlModel, options, receiver); 1.462 + } 1.463 + for (GeneratorBase g : ServiceFinder.find(GeneratorBase.class)) { 1.464 + g.init(wsdlModel, options, receiver); 1.465 + g.doGeneration(); 1.466 + } 1.467 + 1.468 + List<String> implFiles = null; 1.469 + if (options.isGenerateJWS) { 1.470 + implFiles = JwsImplGenerator.generate(wsdlModel, options, receiver); 1.471 + } 1.472 + 1.473 + for (Plugin plugin: options.activePlugins) { 1.474 + try { 1.475 + plugin.run(wsdlModel, options, receiver); 1.476 + } catch (SAXException sex) { 1.477 + // fatal error. error should have been reported 1.478 + return false; 1.479 + } 1.480 + } 1.481 + 1.482 + CodeWriter cw; 1.483 + if (options.filer != null) { 1.484 + cw = new FilerCodeWriter(options.sourceDir, options); 1.485 + } else { 1.486 + cw = new WSCodeWriter(options.sourceDir, options); 1.487 + } 1.488 + 1.489 + if (options.verbose) 1.490 + cw = new ProgressCodeWriter(cw, out); 1.491 + options.getCodeModel().build(cw); 1.492 + 1.493 + if (options.isGenerateJWS) { 1.494 + //move Impl files to implDestDir 1.495 + return JwsImplGenerator.moveToImplDestDir(implFiles, options, receiver); 1.496 + } 1.497 + 1.498 + return true; 1.499 + } 1.500 + 1.501 + public void setEntityResolver(EntityResolver resolver){ 1.502 + this.options.entityResolver = resolver; 1.503 + } 1.504 + 1.505 + /* 1.506 + * To take care of JDK6-JDK6u3, where 2.1 API classes are not there 1.507 + */ 1.508 + private static boolean useBootClasspath(Class clazz) { 1.509 + try { 1.510 + ParallelWorldClassLoader.toJarUrl(clazz.getResource('/'+clazz.getName().replace('.','/')+".class")); 1.511 + return true; 1.512 + } catch(Exception e) { 1.513 + return false; 1.514 + } 1.515 + } 1.516 + 1.517 + protected boolean compileGeneratedClasses(ErrorReceiver receiver, WsimportListener listener){ 1.518 + List<String> sourceFiles = new ArrayList<String>(); 1.519 + 1.520 + for (File f : options.getGeneratedFiles()) { 1.521 + if (f.exists() && f.getName().endsWith(".java")) { 1.522 + sourceFiles.add(f.getAbsolutePath()); 1.523 + } 1.524 + } 1.525 + 1.526 + if (sourceFiles.size() > 0) { 1.527 + String classDir = options.destDir.getAbsolutePath(); 1.528 + String classpathString = createClasspathString(); 1.529 + boolean bootCP = useBootClasspath(EndpointContext.class) || useBootClasspath(JAXBPermission.class); 1.530 + List<String> args = new ArrayList<String>(); 1.531 + args.add("-d"); 1.532 + args.add(classDir); 1.533 + args.add("-classpath"); 1.534 + args.add(classpathString); 1.535 + //javac is not working in osgi as the url starts with a bundle 1.536 + if (bootCP) { 1.537 + args.add("-Xbootclasspath/p:" 1.538 + + JavaCompilerHelper.getJarFile(EndpointContext.class) 1.539 + + File.pathSeparator 1.540 + + JavaCompilerHelper.getJarFile(JAXBPermission.class)); 1.541 + } 1.542 + 1.543 + if (options.debug) { 1.544 + args.add("-g"); 1.545 + } 1.546 + 1.547 + if (options.encoding != null) { 1.548 + args.add("-encoding"); 1.549 + args.add(options.encoding); 1.550 + } 1.551 + 1.552 + if (options.javacOptions != null) { 1.553 + args.addAll(options.getJavacOptions(args, listener)); 1.554 + } 1.555 + 1.556 + for (int i = 0; i < sourceFiles.size(); ++i) { 1.557 + args.add(sourceFiles.get(i)); 1.558 + } 1.559 + 1.560 + listener.message(WscompileMessages.WSIMPORT_COMPILING_CODE()); 1.561 + if(options.verbose){ 1.562 + StringBuilder argstr = new StringBuilder(); 1.563 + for(String arg:args){ 1.564 + argstr.append(arg).append(" "); 1.565 + } 1.566 + listener.message("javac "+ argstr.toString()); 1.567 + } 1.568 + 1.569 + return JavaCompilerHelper.compile(args.toArray(new String[args.size()]), out, receiver); 1.570 + } 1.571 + //there are no files to compile, so return true? 1.572 + return true; 1.573 + } 1.574 + 1.575 + private String createClasspathString() { 1.576 + StringBuilder classpathStr = new StringBuilder(System.getProperty("java.class.path")); 1.577 + for(String s: options.cmdlineJars) { 1.578 + classpathStr.append(File.pathSeparator); 1.579 + classpathStr.append(new File(s).toString()); 1.580 + } 1.581 + return classpathStr.toString(); 1.582 + } 1.583 + 1.584 + protected void usage(Options options) { 1.585 + System.out.println(WscompileMessages.WSIMPORT_HELP(WSIMPORT)); 1.586 + System.out.println(WscompileMessages.WSIMPORT_USAGE_EXTENSIONS()); 1.587 + System.out.println(WscompileMessages.WSIMPORT_USAGE_EXAMPLES()); 1.588 + } 1.589 +}