# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with this
# work for additional information regarding copyright ownership.  The ASF
# licenses this file to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
# License for the specific language governing permissions and limitations under
# the License.


require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helpers'))
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'packaging', 'packaging_helper'))


describe Project, '#manifest' do
  it 'should include user name' do
    ENV['USER'] = 'MysteriousJoe'
    define('foo').manifest['Build-By'].should eql('MysteriousJoe')
  end

  it 'should include JDK version' do
    define('foo').manifest['Build-Jdk'].should =~ /^1\.\d+\.\w+$/
  end

  it 'should include project comment' do
    desc 'My Project'
    define('foo').manifest['Implementation-Title'].should eql('My Project')
  end

  it 'should include project name if no comment' do
    define('foo').manifest['Implementation-Title'].should eql('foo')
  end

  it 'should include project version' do
    define('foo', :version=>'2.1').manifest['Implementation-Version'].should eql('2.1')
  end

  it 'should not include project version unless specified' do
    define('foo').manifest['Implementation-Version'].should be_nil
  end

  it 'should inherit from parent project' do
    define('foo', :version=>'2.1') { define 'bar' }
    project('foo:bar').manifest['Implementation-Version'].should eql('2.1')
  end

end


RSpec.shared_examples 'package with manifest' do
  before do
    @long_line = 'No line may be longer than 72 bytes (not characters), in its UTF8-encoded form. If a value would make the initial line longer than this, it should be continued on extra lines (each starting with a single SPACE).'
  end

  def package_with_manifest(manifest = nil)
    packaging = @packaging
    @project = define('foo', :version=>'1.2') do
      package packaging
      package(packaging).with(:manifest=>manifest) unless manifest.nil?
    end
  end

  def inspect_manifest(package = nil)
    package ||= project('foo').package(@packaging)
    package.invoke
    yield Buildr::Packaging::Java::Manifest.from_zip(package)
  end

  it 'should include default header when no options specified' do
    ENV['USER'] = 'MysteriousJoe'
    package_with_manifest # Nothing for default.
    inspect_manifest do |manifest|
      manifest.sections.size.should be(1)
      manifest.main.should == {
        'Manifest-Version'        =>'1.0',
        'Created-By'              =>'Buildr',
        'Implementation-Title'    =>@project.name,
        'Implementation-Version'  =>'1.2',
        'Build-Jdk'               =>ENV_JAVA['java.version'],
        'Build-By'                =>'MysteriousJoe'
      }
    end
  end

  it 'should not exist when manifest=false' do
    package_with_manifest false
    @project.package(@packaging).invoke
    Zip::File.open(@project.package(@packaging).to_s) do |zip|
      zip.exist?('META-INF/MANIFEST.MF').should be_false
    end
  end

  it 'should generate a new manifest for a file that does not have one' do
    Zip::OutputStream.open 'tmp.zip' do |zip|
      zip.put_next_entry 'empty.txt'
    end
    begin
      manifest = Buildr::Packaging::Java::Manifest.from_zip('tmp.zip')
      manifest.each do |key, val|
        Buildr::Packaging::Java::Manifest::STANDARD_HEADER.should include(key)
      end
    ensure
      rm 'tmp.zip'
    end
  end

  it 'should map manifest from hash' do
    package_with_manifest 'Foo'=>1, :bar=>'Bar'
    inspect_manifest do |manifest|
      manifest.sections.size.should be(1)
      manifest.main['Manifest-Version'].should eql('1.0')
      manifest.main['Created-By'].should eql('Buildr')
      manifest.main['Foo'].should eql('1')
      manifest.main['bar'].should eql('Bar')
    end
  end

  it 'should close the temporary file used for packaging the MANIFEST.MF file' do
    package_with_manifest 'Foo'=>1, :bar=>'Bar'
    package = project('foo').package(@packaging)
    package.invoke
    module AccessManifestTMP
      attr_reader :manifest_tmp
    end
    (package.dup.extend(AccessManifestTMP).manifest_tmp.closed?).should be_true
  end

  it 'should end hash manifest with EOL' do
    package_with_manifest 'Foo'=>1, :bar=>'Bar'
    package = project('foo').package(@packaging)
    package.invoke
    Zip::File.open(package.to_s) { |zip| zip.read('META-INF/MANIFEST.MF').should =~ /#{Buildr::Packaging::Java::Manifest::LINE_SEPARATOR}$/ }
  end

  it 'should break hash manifest lines longer than 72 characters using continuations' do
    package_with_manifest 'foo'=>@long_line
    package = project('foo').package(@packaging)
    inspect_manifest do |manifest|
      manifest.main['foo'].should == @long_line
    end
  end

  it 'should map manifest from array' do
    package_with_manifest [ { :foo=>'first' }, { :bar=>'second' } ]
    inspect_manifest do |manifest|
      manifest.sections.size.should be(2)
      manifest.main['Manifest-Version'].should eql('1.0')
      manifest.main['foo'].should eql('first')
      manifest.sections.last['bar'].should eql('second')
    end
  end

  it 'should end array manifest with EOL' do
    package_with_manifest [ { :foo=>'first' }, { :bar=>'second' } ]
    package = project('foo').package(@packaging)
    package.invoke
    Zip::File.open(package.to_s) { |zip| zip.read('META-INF/MANIFEST.MF')[-1].should == ?\n }
  end

  it 'should break array manifest lines longer than 72 characters using continuations' do
    package_with_manifest ['foo'=>@long_line]
    package = project('foo').package(@packaging)
    inspect_manifest do |manifest|
      manifest.main['foo'].should == @long_line
    end
  end

  it 'should put Name: at beginning of section' do
    package_with_manifest [ {}, { 'Name'=>'first', :Foo=>'first', :bar=>'second' } ]
    package = project('foo').package(@packaging)
    package.invoke
    inspect_manifest do |manifest|
      manifest.sections[1]["Name"].should == "first"
    end
  end

  it 'should create manifest from proc' do
    package_with_manifest lambda { 'Meta: data' }
    inspect_manifest do |manifest|
      manifest.sections.size.should be(1)
      manifest.main['Manifest-Version'].should eql('1.0')
      manifest.main['Meta'].should eql('data')
    end
  end

  it 'should create manifest from file' do
    write 'MANIFEST.MF', 'Meta: data'
    package_with_manifest 'MANIFEST.MF'
    inspect_manifest do |manifest|
      manifest.sections.size.should be(1)
      manifest.main['Meta'].should eql('data')
    end
  end

  it 'should give 644 permissions to the manifest' do
    package_with_manifest  [ {}, { 'Name'=>'first', :Foo=>'first', :bar=>'second' } ]
    package ||= project('foo').package(@packaging)
    package.invoke
    Zip::File.open(package.to_s) do |zip|
      permissions = format("%o", zip.find_entry('META-INF/MANIFEST.MF').unix_perms)
      expected_mode = Buildr::Util.win_os? ? /666$/ : /644$/
      permissions.should match expected_mode
    end
  end

  it 'should not add manifest version twice' do
    write 'MANIFEST.MF', 'Manifest-Version: 1.9'
    package_with_manifest 'MANIFEST.MF'
    package ||= project('foo').package(@packaging)
    package.invoke
    Zip::File.open(package.to_s) do |zip|
      zip.read('META-INF/MANIFEST.MF').scan(/(Manifest-Version)/m).size.should == 1
    end
  end

  it 'should give precedence to version specified in manifest file' do
    write 'MANIFEST.MF', 'Manifest-Version: 1.9'
    package_with_manifest 'MANIFEST.MF'
    inspect_manifest do |manifest|
      manifest.main['Manifest-Version'].should == '1.9'
    end
  end

  it 'should create manifest from task' do
    file 'MANIFEST.MF' do |task|
      write task.to_s, 'Meta: data'
    end
    package_with_manifest 'MANIFEST.MF'
    inspect_manifest do |manifest|
      manifest.sections.size.should be(1)
      manifest.main['Manifest-Version'].should eql('1.0')
      manifest.main['Meta'].should eql('data')
    end
  end

  it 'should respond to with() and accept manifest' do
    write 'DISCLAIMER'
    mkpath 'target/classes'
    packaging = @packaging
    define('foo', :version=>'1.0') { package(packaging).with :manifest=>{'Foo'=>'data'} }
    inspect_manifest { |manifest| manifest.main['Foo'].should eql('data') }
  end

  it 'should include META-INF directory' do
    packaging = @packaging
    package = define('foo', :version=>'1.0') { package(packaging) }.packages.first
    package.invoke
    Zip::File.open(package.to_s) do |zip|
      zip.entries.map(&:to_s).should include('META-INF/')
    end
  end

  it 'should inherit manifest from parent project' do
    packaging = @packaging
    package = nil
    define('foo', :version => '1.0') do
      manifest['Foo'] = '1'
      package(packaging)
      define('bar', :version => '1.0') do
        manifest['bar'] = 'Bar'
        package(:jar)
        package = packages.first
      end
    end
    inspect_manifest(package) do |manifest|
      manifest.sections.size.should be(1)
      manifest.main['Manifest-Version'].should eql('1.0')
      manifest.main['Created-By'].should eql('Buildr')
      manifest.main['Foo'].should eql('1')
      manifest.main['bar'].should eql('Bar')
    end
  end

  it 'should not modify manifest of parent project' do
    packaging = @packaging
    define('foo', :version => '1.0') do
      manifest['Foo'] = '1'
      package(packaging)
      define('bar', :version => '1.0') do
        manifest['bar'] = 'Bar'
        package(:jar)
      end
      define('baz', :version => '1.0') do
        manifest['baz'] = 'Baz'
        package(:jar)
      end
    end
    inspect_manifest(project('foo').packages.first) do |manifest|
      manifest.sections.size.should be(1)
      manifest.main['Manifest-Version'].should eql('1.0')
      manifest.main['Created-By'].should eql('Buildr')
      manifest.main['Foo'].should eql('1')
      manifest.main['bar'].should be_nil
      manifest.main['baz'].should be_nil
    end
    inspect_manifest(project('foo:bar').packages.first) do |manifest|
      manifest.sections.size.should be(1)
      manifest.main['Manifest-Version'].should eql('1.0')
      manifest.main['Created-By'].should eql('Buildr')
      manifest.main['Foo'].should eql('1')
      manifest.main['bar'].should eql('Bar')
      manifest.main['baz'].should be_nil
    end
    inspect_manifest(project('foo:baz').packages.first) do |manifest|
      manifest.sections.size.should be(1)
      manifest.main['Manifest-Version'].should eql('1.0')
      manifest.main['Created-By'].should eql('Buildr')
      manifest.main['Foo'].should eql('1')
      manifest.main['baz'].should eql('Baz')
      manifest.main['bar'].should be_nil
    end
  end
end


describe Project, '#meta_inf' do
  it 'should by an array' do
    define('foo').meta_inf.should be_kind_of(Array)
  end

  it 'should include LICENSE file if found' do
    write 'LICENSE'
    define('foo').meta_inf.first.should point_to_path('LICENSE')
  end

  it 'should be empty unless LICENSE exists' do
    define('foo').meta_inf.should be_empty
  end

  it 'should inherit from parent project' do
    write 'LICENSE'
    define('foo') { define 'bar' }
    project('foo:bar').meta_inf.first.should point_to_path('LICENSE')
  end

  it 'should expect LICENSE file parent project' do
    write 'bar/LICENSE'
    define('foo') { define 'bar' }
    project('foo:bar').meta_inf.should be_empty
  end
end


RSpec.shared_examples 'package with meta_inf' do

  def package_with_meta_inf(meta_inf = nil)
    packaging = @packaging
    @project = Buildr.define('foo', :version=>'1.2') do
      package packaging
      package(packaging).with(:meta_inf=>meta_inf) if meta_inf
    end
  end

  def inspect_meta_inf
    package = project('foo').package(@packaging)
    package.invoke
    assumed = Array(@meta_inf_ignore)
    Zip::File.open(package.to_s) do |zip|
      entries = zip.entries.map(&:name).select { |f| File.dirname(f) == 'META-INF' }.map { |f| File.basename(f) }
      assumed.each { |f| entries.should include(f) }
      yield entries - assumed if block_given?
    end
  end

  it 'should default to LICENSE file' do
    write 'LICENSE'
    package_with_meta_inf
    inspect_meta_inf { |files| files.should eql(['LICENSE']) }
  end

  it 'should be empty if no LICENSE file' do
    package_with_meta_inf
    inspect_meta_inf { |files| files.should be_empty }
  end

  it 'should include file specified by :meta_inf option' do
    write 'README'
    package_with_meta_inf 'README'
    inspect_meta_inf { |files| files.should eql(['README']) }
  end

  it 'should include files specified by :meta_inf option' do
    files = ['README', 'DISCLAIMER'].each { |file| write file }
    package_with_meta_inf files
    inspect_meta_inf { |files| files.should eql(files) }
  end

  it 'should include file task specified by :meta_inf option' do
    file('README') { |task| write task.to_s }
    package_with_meta_inf file('README')
    inspect_meta_inf { |files| files.should eql(['README']) }
  end

  it 'should include file tasks specified by :meta_inf option' do
    files = ['README', 'DISCLAIMER'].each { |file| file(file) { |task| write task.to_s } }
    package_with_meta_inf files.map { |f| file(f) }
    inspect_meta_inf { |files| files.should eql(files) }
  end

  it 'should complain if cannot find file' do
    package_with_meta_inf 'README'
    lambda { inspect_meta_inf }.should raise_error(RuntimeError, /README/)
  end

  it 'should complain if cannot build task' do
    file('README')  { fail 'Failed' }
    package_with_meta_inf 'README'
    lambda { inspect_meta_inf }.should raise_error(RuntimeError, /Failed/)
  end

  it 'should respond to with() and accept manifest and meta_inf' do
    write 'DISCLAIMER'
    mkpath 'target/classes'
    packaging = @packaging
    define('foo', :version=>'1.0') { package(packaging).with :meta_inf=>'DISCLAIMER' }
    inspect_meta_inf { |files| files.should eql(['DISCLAIMER']) }
  end
end


describe Packaging, 'jar' do
  it_should_behave_like 'packaging'
  before { @packaging = :jar }
  it_should_behave_like 'package with manifest'
  it_should_behave_like 'package with meta_inf'
  before { @meta_inf_ignore = 'MANIFEST.MF' }

  it 'should place the manifest as the first entry of the file' do
    write 'src/main/java/Test.java', 'class Test {}'
    define('foo', :version=>'1.0') { package(:jar) }
    project('foo').package(:jar).invoke
    Zip::File.open(project('foo').package(:jar).to_s) do |jar|
      entries_to_s = jar.entries.map(&:to_s).delete_if {|entry| entry[-1,1] == "/"}
      # Sometimes META-INF/ is counted as first entry, which is fair game.
      (entries_to_s.first == 'META-INF/MANIFEST.MF' || entries_to_s[1] == 'META-INF/MANIFEST.MF').should be_true
    end
  end

  it 'should use files from compile directory if nothing included' do
    write 'src/main/java/Test.java', 'class Test {}'
    define('foo', :version=>'1.0') { package(:jar) }
    project('foo').package(:jar).invoke
    Zip::File.open(project('foo').package(:jar).to_s) do |jar|
      jar.entries.map(&:to_s).sort.should include('META-INF/MANIFEST.MF', 'Test.class')
    end
  end

  it 'should use files from resources directory if nothing included' do
    write 'src/main/resources/test/important.properties'
    define('foo', :version=>'1.0') { package(:jar) }
    project('foo').package(:jar).invoke
    Zip::File.open(project('foo').package(:jar).to_s) do |jar|
      jar.entries.map(&:to_s).sort.should include('test/important.properties')
    end
  end

  it 'should include class directories' do
    write 'src/main/java/code/Test.java', 'package code ; class Test {}'
    define('foo', :version=>'1.0') { package(:jar) }
    project('foo').package(:jar).invoke
    Zip::File.open(project('foo').package(:jar).to_s) do |jar|
      jar.entries.map(&:to_s).sort.should include('code/')
    end
  end

  it 'should include resource files starting with dot' do
    write 'src/main/resources/test/.config'
    define('foo', :version=>'1.0') { package(:jar) }
    project('foo').package(:jar).invoke
    Zip::File.open(project('foo').package(:jar).to_s) do |jar|
      jar.entries.map(&:to_s).sort.should include('test/.config')
    end
  end

  it 'should include empty resource directories' do
    mkpath 'src/main/resources/empty'
    define('foo', :version=>'1.0') { package(:jar) }
    project('foo').package(:jar).invoke
    Zip::File.open(project('foo').package(:jar).to_s) do |jar|
      jar.entries.map(&:to_s).sort.should include('empty/')
    end
  end

  it 'should raise error when calling with() with nil value' do
    lambda {
      define('foo', :version=>'1.0') { package(:jar).with(nil) }
    }.should raise_error
  end

  it 'should exclude resources when ordered to do so' do
    write 'src/main/resources/foo.xml', ''
    foo = define('foo', :version => '1.0') { package(:jar).exclude('foo.xml')}
    foo.package(:jar).invoke
    Zip::File.open(foo.package(:jar).to_s) do |jar|
      jar.entries.map(&:to_s).sort.should_not include('foo.xml')
    end
  end

end


describe Packaging, 'war' do
  it_should_behave_like 'packaging'
  before { @packaging = :war }
  it_should_behave_like 'package with manifest'
  it_should_behave_like 'package with meta_inf'
  before { @meta_inf_ignore = 'MANIFEST.MF' }

  def make_jars
    artifact('group:id:jar:1.0') { |t| write t.to_s }
    artifact('group:id:jar:2.0') { |t| write t.to_s }
  end

  def inspect_war
    project('foo').package(:war).invoke
    Zip::File.open(project('foo').package(:war).to_s) do |war|
      yield war.entries.map(&:to_s).sort
    end
  end

  it 'should use files from webapp directory if nothing included' do
    write 'src/main/webapp/test.html'
    define('foo', :version=>'1.0') { package(:war) }
    inspect_war { |files| files.should include('test.html') }
  end

  it 'should use files from added assets directory if nothing included' do
    write 'generated/main/webapp/test.html'
    define('foo', :version => '1.0') { assets.paths << 'generated/main/webapp/'; package(:war) }
    inspect_war { |files| files.should include('test.html') }
  end

  it 'should use files from generated assets directory if nothing included' do
    write 'generated/main/webapp/test.html'
    define('foo', :version => '1.0') do
      target_dir = _('generated/main/webapp')
      assets.paths << project.file(target_dir) do
        mkdir_p target_dir
        touch "#{target_dir}/test.html"
        touch target_dir
      end
      package(:war)
    end
    inspect_war { |files| files.should include('test.html') }
  end

  it 'should accept files from :classes option', :retry => (Buildr::Util.win_os? ? 4 : 1) do
    write 'classes/test'
    define('foo', :version=>'1.0') { package(:war).with(:classes=>'classes') }
    inspect_war { |files| files.should include('WEB-INF/classes/test') }
  end

  it 'should use files from compile directory if nothing included' do
    write 'src/main/java/Test.java', 'class Test {}'
    define('foo', :version=>'1.0') { package(:war) }
    inspect_war { |files| files.should include('WEB-INF/classes/Test.class') }
  end

  it 'should ignore compile directory if no source files to compile' do
    define('foo', :version=>'1.0') { package(:war) }
    inspect_war { |files| files.should_not include('target/classes') }
  end

  it 'should include only specified classes directories' do
    write 'src/main/java'
    define('foo', :version=>'1.0') { package(:war).with :classes=>_('additional') }
    project('foo').package(:war).classes.should_not include(project('foo').file('target/classes'))
    project('foo').package(:war).classes.should include(project('foo').file('additional'))
  end

  it 'should use files from resources directory if nothing included' do
    write 'src/main/resources/test/important.properties'
    define('foo', :version=>'1.0') { package(:war) }
    inspect_war { |files| files.should include('WEB-INF/classes/test/important.properties') }
  end

  it 'should include empty resource directories' do
    mkpath 'src/main/resources/empty'
    define('foo', :version=>'1.0') { package(:war) }
    inspect_war { |files| files.should include('WEB-INF/classes/empty/') }
  end

  it 'should accept file from :libs option' do
    write 'lib/foo.jar'
    define('foo', :version=>'1.0') { package(:war).libs << 'lib/foo.jar' }
    inspect_war { |files| files.should include('META-INF/MANIFEST.MF', 'WEB-INF/lib/foo.jar') }
  end


  it 'should accept artifacts from :libs option' do
    make_jars
    define('foo', :version=>'1.0') { package(:war).with(:libs=>'group:id:jar:1.0') }
    inspect_war { |files| files.should include('META-INF/MANIFEST.MF', 'WEB-INF/lib/id-1.0.jar') }
  end

  it 'should accept artifacts from :libs option' do
    make_jars
    define('foo', :version=>'1.0') { package(:war).with(:libs=>['group:id:jar:1.0', 'group:id:jar:2.0']) }
    inspect_war { |files| files.should include('META-INF/MANIFEST.MF', 'WEB-INF/lib/id-1.0.jar', 'WEB-INF/lib/id-2.0.jar') }
  end

  it 'should use artifacts from compile classpath if no libs specified' do
    make_jars
    define('foo', :version=>'1.0') { compile.with 'group:id:jar:1.0', 'group:id:jar:2.0' ; package(:war) }
    inspect_war { |files| files.should include('META-INF/MANIFEST.MF', 'WEB-INF/lib/id-1.0.jar', 'WEB-INF/lib/id-2.0.jar') }
  end

  it 'should use artifacts from compile classpath if no libs specified, leaving the user specify which to exclude as files' do
    make_jars
    define('foo', :version=>'1.0') { compile.with 'group:id:jar:1.0', 'group:id:jar:2.0' ; package(:war).path('WEB-INF/lib').exclude('id-2.0.jar')  }
    inspect_war { |files| files.should include('META-INF/MANIFEST.MF', 'WEB-INF/lib/id-1.0.jar') }
  end

  it 'should use artifacts from compile classpath if no libs specified, leaving the user specify which to exclude as files with glob expressions' do
    make_jars
    define('foo', :version=>'1.0') { compile.with 'group:id:jar:1.0', 'group:id:jar:2.0' ; package(:war).path('WEB-INF/lib').exclude('**/id-2.0.jar')   }
    inspect_war { |files| files.should include('META-INF/MANIFEST.MF', 'WEB-INF/lib/id-1.0.jar') }
  end

  it 'should exclude files regardless of the path where they are included, using wildcards' do
    make_jars
    define('foo', :version=>'1.0') { compile.with 'group:id:jar:1.0', 'group:id:jar:2.0' ; package(:war).exclude('**/id-2.0.jar')   }
    inspect_war { |files| files.should include('META-INF/MANIFEST.MF', 'WEB-INF/lib/id-1.0.jar') }
  end

  it 'should exclude files regardless of the path where they are included, specifying target path entirely' do
     make_jars
     define('foo', :version=>'1.0') { compile.with 'group:id:jar:1.0', 'group:id:jar:2.0' ; package(:war).exclude('WEB-INF/lib/id-2.0.jar')   }
     inspect_war { |files| files.should include('META-INF/MANIFEST.MF', 'WEB-INF/lib/id-1.0.jar') }
   end

  it 'should exclude files regardless of the path where they are included for war files' do
     write 'src/main/java/com/example/included/Test.java', 'package com.example.included; class Test {}'
     write 'src/main/java/com/example/excluded/Test.java', 'package com.example.excluded; class Test {}'
     define('foo', :version=>'1.0') do
       package(:war).enhance do |war|
         war.exclude('WEB-INF/classes/com/example/excluded/**.class')
       end
     end
     inspect_war do |files|
       files.should include('WEB-INF/classes/com/example/included/Test.class')
       files.should_not include('WEB-INF/classes/com/example/excluded/Test.class')
     end
   end

  it 'should include only specified libraries' do
    define 'foo', :version=>'1.0' do
      compile.with 'group:id:jar:1.0'
      package(:war).with :libs=>'additional:id:jar:1.0'
    end
    project('foo').package(:war).libs.should_not include(artifact('group:id:jar:1.0'))
    project('foo').package(:war).libs.should include(artifact('additional:id:jar:1.0'))
  end

end


describe Packaging, 'aar' do
  it_should_behave_like 'packaging'
  before { @packaging = :aar }
  it_should_behave_like 'package with manifest'
  it_should_behave_like 'package with meta_inf'
  before do
    write 'src/main/axis2/services.xml'
    @meta_inf_ignore = ['MANIFEST.MF', 'services.xml']
  end

  def make_jars
    artifact('group:id:jar:1.0') { |t| write t.to_s }
    artifact('group:id:jar:2.0') { |t| write t.to_s }
  end

  def inspect_aar
    project('foo').package(:aar).invoke
    Zip::File.open(project('foo').package(:aar).to_s) do |aar|
      yield aar.entries.map(&:to_s).sort
    end
  end

  it 'should automatically include services.xml and any *.wsdl files under src/main/axis2' do
    write 'src/main/axis2/my-service.wsdl'
    define('foo', :version=>'1.0') { package(:aar) }
    inspect_aar { |files| files.should include('META-INF/MANIFEST.MF', 'META-INF/services.xml', 'META-INF/my-service.wsdl') }
  end

  it 'should accept files from :include option' do
    write 'test'
    define('foo', :version=>'1.0') { package(:aar).include 'test' }
    inspect_aar { |files| files.should include('META-INF/MANIFEST.MF', 'test') }
  end

  it 'should use files from compile directory if nothing included' do
    write 'src/main/java/Test.java', 'class Test {}'
    define('foo', :version=>'1.0') { package(:aar) }
    inspect_aar { |files| files.should include('Test.class') }
  end

  it 'should use files from resources directory if nothing included' do
    write 'src/main/resources/test/important.properties'
    define('foo', :version=>'1.0') { package(:aar) }
    inspect_aar { |files| files.should include('test/important.properties') }
  end

  it 'should include empty resource directories' do
    mkpath 'src/main/resources/empty'
    define('foo', :version=>'1.0') { package(:aar) }
    inspect_aar { |files| files.should include('empty/') }
  end

  it 'should accept file from :libs option' do
    make_jars
    define('foo', :version=>'1.0') { package(:aar).with :libs=>'group:id:jar:1.0' }
    inspect_aar { |files| files.should include('META-INF/MANIFEST.MF', 'lib/id-1.0.jar') }
  end

  it 'should accept file from :libs option' do
    make_jars
    define('foo', :version=>'1.0') { package(:aar).with :libs=>['group:id:jar:1.0', 'group:id:jar:2.0'] }
    inspect_aar { |files| files.should include('META-INF/MANIFEST.MF', 'lib/id-1.0.jar', 'lib/id-2.0.jar') }
  end

  it 'should NOT use artifacts from compile classpath if no libs specified' do
    make_jars
    define('foo', :version=>'1.0') { compile.with 'group:id:jar:1.0', 'group:id:jar:2.0' ; package(:aar) }
    inspect_aar { |files| files.should include('META-INF/MANIFEST.MF') }
  end

  it 'should return all libraries from libs attribute' do
    define 'foo', :version=>'1.0' do
      compile.with 'group:id:jar:1.0'
      package(:aar).with :libs=>'additional:id:jar:1.0'
    end
    project('foo').package(:aar).libs.should_not include(artifact('group:id:jar:1.0'))
    project('foo').package(:aar).libs.should include(artifact('additional:id:jar:1.0'))
  end

end


describe Packaging, 'ear' do
  it_should_behave_like 'packaging'
  before { @packaging = :ear }
  it_should_behave_like 'package with manifest'
  it_should_behave_like 'package with meta_inf'
  before { @meta_inf_ignore = ['MANIFEST.MF', 'application.xml'] }

  def inspect_ear
    project('foo').package(:ear).invoke
    Zip::File.open(project('foo').package(:ear).to_s) do |ear|
      yield ear.entries.map(&:to_s).sort
    end
  end

  def inspect_application_xml
    project('foo').package(:ear).invoke
    Zip::File.open(project('foo').package(:ear).to_s) do |ear|
      yield REXML::Document.new(ear.read('META-INF/application.xml')).root
    end
  end

  def inspect_classpath(package)
    project('foo').package(:ear).invoke
    Zip::File.open(project('foo').package(:ear).to_s) do |ear|
      File.open('tmp.zip', 'wb') do |tmp|
        tmp.write ear.read(package)
      end
      manifest = Buildr::Packaging::Java::Manifest.from_zip('tmp.zip')
      yield manifest.main['Class-Path'].split(' ')
    end
  end

  it 'should set display name from project id' do
    define 'foo', :version=>'1.0' do
      package(:ear).display_name.should eql('foo')
      define 'bar' do
        package(:ear).display_name.should eql('foo-bar')
      end
    end
  end

  it 'should set display name in application.xml' do
    define 'foo', :version=>'1.0' do
      package(:ear)
    end
    inspect_application_xml { |xml| xml.get_text('/application/display-name').should == 'foo' }
  end

  it 'should accept different display name' do
    define 'foo', :version=>'1.0' do
      package(:ear).display_name = 'bar'
    end
    inspect_application_xml { |xml| xml.get_text('/application/display-name').should == 'bar' }
  end

  it 'should set description in application.xml to project comment if not specified' do
    desc "MyDescription"
    define 'foo', :version=>'1.0' do
      package(:ear)
    end
    inspect_application_xml { |xml| xml.get_text('/application/description').should == 'MyDescription' }
  end

  it 'should not set description in application.xml if not specified and no project comment' do
    define 'foo', :version=>'1.0' do
      package(:ear)
    end
    inspect_application_xml { |xml| xml.get_text('/application/description').should be_nil }
  end

  it 'should set description in application.xml if specified' do
    define 'foo', :version=>'1.0' do
      package(:ear).description = "MyDescription"
    end
    inspect_application_xml { |xml| xml.get_text('/application/description').should == 'MyDescription' }
  end

  it 'should add security-roles to application.xml if given' do
    define 'foo', :version=>'1.0' do
	  package(:ear).security_roles << {:id=>'sr1',
		:description=>'System Administrator', :name=>'systemadministrator'}
	end
	inspect_application_xml do |xml|
		xml.get_text("/application/security-role[@id='sr1']/description").to_s.should eql('System Administrator')
		xml.get_text("/application/security-role[@id='sr1']/role-name").to_s.should eql('systemadministrator')
	end
  end

  it 'should map WARs to /war directory' do
    define 'foo', :version=>'1.0' do
      package(:ear) << package(:war)
    end
    inspect_ear { |files| files.should include('war/foo-1.0.war') }
  end

  it 'should map EJBs to /ejb directory' do
    define 'foo', :version=>'1.0' do
      package(:ear).add :ejb=>package(:jar)
    end
    inspect_ear { |files| files.should include('ejb/foo-1.0.jar') }
  end

  it 'should not modify original artifact for its components' do
    define 'one', :version => '1.0' do
      write 'src/main/resources/one.txt', '1'
      package(:jar)
    end

    define 'two', :version => '1.0' do
      write 'src/main/resources/two.txt', '2'
      package(:jar)
    end

    define 'foo', :version => '1.0' do
      package(:ear).add project(:one).package(:jar)
      package(:ear).add :ejb => project(:two).package(:jar)
    end

    inspect_ear { |files| files.should include('lib/one-1.0.jar', 'ejb/two-1.0.jar') }

    Buildr::Packaging::Java::Manifest.from_zip(project('one').package(:jar)).main['Class-Path'].should be_nil
    Buildr::Packaging::Java::Manifest.from_zip(project('two').package(:jar)).main['Class-Path'].should be_nil

    inspect_classpath 'ejb/two-1.0.jar' do |classpath|
      classpath.should include('../lib/one-1.0.jar')
    end
  end

  it 'should map JARs to /lib directory' do
    define 'foo', :version=>'1.0' do
      package(:ear) << package(:jar)
    end
    inspect_ear { |files| files.should include('lib/foo-1.0.jar') }
  end

  it 'should accept component type with :type option' do
    define 'foo', :version=>'1.0' do
      package(:ear).add package(:jar), :type=>:ejb
    end
    inspect_ear { |files| files.should include('ejb/foo-1.0.jar') }
  end

  it 'should accept component and its type as type=>artifact' do
    define 'foo', :version=>'1.0' do
      package(:ear).add :ejb=>package(:jar)
    end
    inspect_ear { |files| files.should include('ejb/foo-1.0.jar') }
  end

  it 'should map typed JARs to /jar directory' do
    define 'foo', :version=>'1.0' do
      package(:ear).add :jar=>package(:jar)
    end
    inspect_ear { |files| files.should include('jar/foo-1.0.jar') }
  end

  it 'should add multiple components at a time using the type=>component style' do
    define 'bar', :version => '1.5' do
      package(:war) # must be added as a webapp
      package(:jar) # must be added as a shared lib
      package(:zip) # this one should be excluded
    end
    define 'baz', :version => '1.5' do
      package(:jar, :id => 'one')
      package(:jar, :id => 'two')
    end
    define 'foo', :version => '1.0' do
      package(:ear).add :lib => project('baz'),
                        :war => project('bar').package(:war),
                        :ejb => project('bar').package(:jar)
    end
    inspect_ear do |files|
      files.should include(*%w{ lib/one-1.5.jar lib/two-1.5.jar war/bar-1.5.war ejb/bar-1.5.jar  })
      files.should_not satisfy { files.any? { |f| f =~ /\.zip$/ } }
    end
  end

  it 'should add all EAR supported packages when given a project argument' do
    define 'bar', :version => '1.5' do
      package(:war) # must be added as a webapp
      package(:jar) # must be added as a shared lib
      package(:zip) # this one should be excluded
    end
    define 'baz', :version => '1.5' do
      package(:war)
      package(:jar)
    end
    define 'foo', :version => '1.0' do
      package(:ear).add projects(:bar, :baz)
    end
    inspect_ear do |files|
      files.should include('war/bar-1.5.war', 'lib/bar-1.5.jar', 'lib/baz-1.5.jar', 'war/baz-1.5.war')
      files.should_not satisfy { files.any? { |f| f =~ /\.zip$/ } }
    end
  end

  it 'should complain about unknown component type' do
    define 'foo', :version=>'1.0' do
      lambda { package(:ear).add package(:zip) }.should raise_error(RuntimeError, /ear component/i)
    end
  end

  it 'should allow unknown component types with explicit type' do
    define 'foo', :version=>'1.0' do
      package(:ear).add :lib=>package(:zip)
    end
    inspect_ear { |files| files.should include('lib/foo-1.0.zip') }
  end

  it 'should accept alternative directory name' do
    define 'foo', :version=>'1.0' do
      package(:ear).add package(:jar), :path=>'trash'
    end
    inspect_ear { |files| files.should include('trash/foo-1.0.jar') }
  end

  it 'should accept customization of directory map' do
    define 'foo', :version=>'1.0' do
      package(:ear).dirs[:jar] = 'jarred'
      package(:ear).add :jar=>package(:jar)
    end
    inspect_ear { |files| files.should include('jarred/foo-1.0.jar') }
  end

  it 'should accept customization of directory map with nil paths in application.xml' do
    define 'foo', :version=>'1.0' do
      package(:ear).dirs[:war] = nil
      package(:ear).add :war=>package(:war)
      package(:ear).add package(:jar)
    end
    inspect_ear { |files| files.should include('foo-1.0.war') }
    inspect_application_xml do |xml|
      xml.get_text("/application/module[@id='foo']/web/web-uri").to_s.should eql('foo-1.0.war')
    end
  end

  it 'should accept customization of directory map with nil paths in the classpath' do
    define 'foo', :version=>'1.0' do
      package(:ear).dirs[:lib] = nil
      package(:ear).add :war=>package(:war)
      package(:ear) << package(:jar)
    end
    inspect_classpath 'war/foo-1.0.war' do |classpath|
      classpath.should include('../foo-1.0.jar')
    end
  end

  it 'should list WAR components in application.xml' do
    define 'foo', :version=>'1.0' do
      package(:ear) << package(:war) << package(:war, :id=>'bar')
    end
    inspect_application_xml do |xml|
      xml.get_elements("/application/module[@id='foo'][web]").should_not be_empty
      xml.get_elements("/application/module[@id='bar'][web]").should_not be_empty
    end
  end

  it 'should specify web-uri for WAR components in application.xml' do
    define 'foo', :version=>'1.0' do
      package(:ear) << package(:war)
      package(:ear).add package(:war, :id=>'bar'), :path=>'ws'
    end
    inspect_application_xml do |xml|
      xml.get_text("/application/module[@id='foo']/web/web-uri").to_s.should eql('war/foo-1.0.war')
      xml.get_text("/application/module[@id='bar']/web/web-uri").to_s.should eql('ws/bar-1.0.war')
    end
  end

  it 'should specify context-root for WAR components in application.xml' do
    define 'foo', :version=>'1.0' do
      package(:ear) << package(:war)
      package(:ear).add package(:war, :id=>'bar')
    end
    inspect_application_xml do |xml|
      xml.get_text("/application/module[@id='foo']/web/context-root").to_s.should eql('/foo')
      xml.get_text("/application/module[@id='bar']/web/context-root").to_s.should eql('/bar')
    end
  end

  it 'should accept context-root for WAR components in application.xml' do
    define 'foo', :version=>'1.0' do
      package(:ear).add package(:war), :context_root=>'rooted'
    end
    inspect_application_xml do |xml|
      xml.get_text("/application/module[@id='foo']/web/context-root").to_s.should eql('/rooted')
    end
  end

  it 'should allow disabling the context root' do
    define 'foo', :version=>'1.0' do
      package(:ear).add package(:war), :context_root=>false
    end
    inspect_application_xml do |xml|
      xml.get_elements("/application/module[@id='foo']/web/context-root").should be_empty
    end
  end

  it 'should list EJB components in application.xml' do
    define 'foo', :version=>'1.0' do
      package(:ear).add :ejb=>package(:jar)
      package(:ear).add :ejb=>package(:jar, :id=>'bar')
    end
    inspect_application_xml do |xml|
      xml.get_text("/application/module[@id='foo']/ejb").to_s.should eql('ejb/foo-1.0.jar')
      xml.get_text("/application/module[@id='bar']/ejb").to_s.should eql('ejb/bar-1.0.jar')
    end
  end

  it 'should list JAR components in application.xml' do
    define 'foo', :version=>'1.0' do
      package(:ear) << { :jar=>package(:jar) } << { :jar=>package(:jar, :id=>'bar') }
    end
    inspect_application_xml do |xml|
      jars = xml.get_elements('/application/jar').map(&:texts).map(&:join)
      jars.should include('jar/foo-1.0.jar', 'jar/bar-1.0.jar')
    end
  end

  it 'should update WAR component classpath to include libraries' do
    define 'foo', :version=>'1.0' do
      package(:ear) << package(:jar, :id=>'lib1') << package(:jar, :id=>'lib2')
      package(:ear).add package(:war)
    end
    inspect_classpath 'war/foo-1.0.war' do |classpath|
      classpath.should include('../lib/lib1-1.0.jar', '../lib/lib2-1.0.jar')
    end
  end

  it 'should update WAR component classpath but skip internal libraries' do
    define 'foo', :version=>'1.0' do
      package(:ear) << package(:jar, :id=>'lib1') << package(:jar, :id=>'lib2')
      package(:war).with(:libs=>package(:jar, :id=>'lib1'))
      package(:ear).add package(:war)
    end
    inspect_classpath 'war/foo-1.0.war' do |classpath|
      classpath.should_not include('../lib/lib1-1.0.jar')
      classpath.should include('../lib/lib2-1.0.jar')
    end
  end

  it 'should update EJB component classpath to include libraries' do
    define 'foo', :version=>'1.0' do
      package(:ear) << package(:jar, :id=>'lib1') << package(:jar, :id=>'lib2')
      package(:ear).add :ejb=>package(:jar, :id=>'foo')
    end
    inspect_classpath 'ejb/foo-1.0.jar' do |classpath|
      classpath.should include('../lib/lib1-1.0.jar', '../lib/lib2-1.0.jar')
    end
  end

  it 'should update JAR component classpath to include libraries' do
    define 'foo', :version=>'1.0' do
      package(:ear) << package(:jar, :id=>'lib1') << package(:jar, :id=>'lib2')
      package(:ear).add :jar=>package(:jar, :id=>'foo')
    end
    inspect_classpath 'jar/foo-1.0.jar' do |classpath|
      classpath.should include('../lib/lib1-1.0.jar', '../lib/lib2-1.0.jar')
    end
  end

  it 'should deal with very long classpaths' do
    define 'foo', :version=>'1.0' do
      20.times { |i| package(:ear) << package(:jar, :id=>"lib#{i}") }
      package(:ear).add :jar=>package(:jar, :id=>'foo')
    end
    inspect_classpath 'jar/foo-1.0.jar' do |classpath|
      classpath.should include('../lib/lib1-1.0.jar', '../lib/lib2-1.0.jar')
    end
  end


  it 'should generate relative classpaths for top level EJB' do
    define 'foo', :version => '1.0' do
      package(:ear).add package(:jar, :id => 'one'), :path => '.'
      package(:ear).add package(:jar, :id => 'two'), :path => 'dos'
      package(:ear).add package(:jar, :id => 'three'), :path => 'tres'
      package(:ear).add :ejb => package(:jar, :id => 'ejb1'), :path => '.'
    end
    inspect_classpath 'ejb1-1.0.jar' do |classpath|
      classpath.should include(*%w{ one-1.0.jar dos/two-1.0.jar tres/three-1.0.jar })
    end
  end

  it 'should generate relative classpaths for second level EJB' do
    define 'foo', :version => '1.0' do
      package(:ear).add package(:jar, :id => 'one'), :path => '.'
      package(:ear).add package(:jar, :id => 'two'), :path => 'dos'
      package(:ear).add package(:jar, :id => 'three'), :path => 'tres'
      package(:ear).add :ejb => package(:jar, :id => 'ejb2'), :path => 'dos'
    end
    inspect_classpath 'dos/ejb2-1.0.jar' do |classpath|
      classpath.should include(*%w{ ../one-1.0.jar two-1.0.jar ../tres/three-1.0.jar })
    end
  end

  it 'should generate relative classpaths for nested EJB' do
    define 'foo', :version => '1.0' do
      package(:ear).add package(:jar, :id => 'one'), :path => '.'
      package(:ear).add package(:jar, :id => 'two'), :path => 'dos'
      package(:ear).add package(:jar, :id => 'three'), :path => 'dos/tres'
      package(:ear).add package(:jar, :id => 'four'), :path => 'dos/cuatro'
      package(:ear).add :ejb => package(:jar, :id => 'ejb4'), :path => 'dos/cuatro'
    end
    inspect_classpath 'dos/cuatro/ejb4-1.0.jar' do |classpath|
      classpath.should include(*%w{ ../../one-1.0.jar ../two-1.0.jar ../tres/three-1.0.jar four-1.0.jar })
    end
  end

end


describe Packaging, 'sources' do
  it_should_behave_like 'packaging'
  before { @packaging, @package_type = :sources, :jar }

  it 'should create package of type :jar and classifier \'sources\'' do
    define 'foo', :version=>'1.0' do
      package(:sources).type.should eql(:jar)
      package(:sources).classifier.should eql('sources')
      package(:sources).name.should match(/foo-1.0-sources.jar$/)
    end
  end

  it 'should contain source and resource files' do
    write 'src/main/java/Source.java'
    write 'src/main/resources/foo.properties', 'foo=bar'
    define('foo', :version=>'1.0') { package(:sources) }
    project('foo').task('package').invoke
    project('foo').packages.first.should contain('Source.java')
    project('foo').packages.first.should contain('foo.properties')
  end

  it 'should create sources jar if resources exists (but not sources)' do
    write 'src/main/resources/foo.properties', 'foo=bar'
    define('foo', :version=>'1.0') { package(:sources) }
    project('foo').package(:sources).invoke
    project('foo').packages.first.should contain('foo.properties')
  end

  it 'should be a ZipTask' do
    define 'foo', :version=>'1.0' do
      package(:sources).should be_kind_of(ZipTask)
    end
  end
end

describe Packaging, 'javadoc' do
  it_should_behave_like 'packaging'
  before { @packaging, @package_type = :javadoc, :jar }

  it 'should create package of type :zip and classifier \'javadoc\'' do
    define 'foo', :version=>'1.0' do
      package(:javadoc).type.should eql(:jar)
      package(:javadoc).classifier.should eql('javadoc')
      package(:javadoc).name.pathmap('%f').should eql('foo-1.0-javadoc.jar')
    end
  end

  it 'should contain Javadocs' do
    write 'src/main/java/Source.java', 'public class Source {}'
    define('foo', :version=>'1.0') { package(:javadoc) }
    project('foo').task('package').invoke
    project('foo').packages.first.should contain('Source.html', 'index.html')
  end

  it 'should use project description in window title' do
    write 'src/main/java/Source.java', 'public class Source {}'
    desc 'My Project'
    define('foo', :version=>'1.0') { package(:javadoc) }
    project('foo').task('package').invoke
    project('foo').packages.first.entry('index.html').should contain('My Project')
  end

  it 'should be a ZipTask' do
    define 'foo', :version=>'1.0' do
      package(:javadoc).should be_kind_of(ZipTask)
    end
  end
end

describe Packaging, 'test_jar' do
  it_should_behave_like 'packaging'
  before { @packaging, @package_type = :test_jar, :jar }

  it 'should create package of type :jar and classifier \'tests\'' do
    define 'foo', :version=>'1.0' do
      package(:test_jar).type.should eql(:jar)
      package(:test_jar).classifier.should eql('tests')
      package(:test_jar).name.should match(/foo-1.0-tests.jar$/)
    end
  end

  it 'should contain test source and resource files' do
    write 'src/test/java/Test.java', 'public class Test {}'
    write 'src/test/resources/test.properties', 'foo=bar'
    define('foo', :version=>'1.0') { package(:test_jar) }
    project('foo').task('package').invoke
    project('foo').packages.first.should contain('Test.class')
    project('foo').packages.first.should contain('test.properties')
  end

  it 'should create test jar if resources exists (but not sources)' do
    write 'src/test/resources/test.properties', 'foo=bar'
    define('foo', :version=>'1.0') { package(:test_jar) }
    project('foo').package(:test_jar).invoke
    project('foo').packages.first.should contain('test.properties')
  end

  it 'should be a ZipTask' do
    define 'foo', :version=>'1.0' do
      package(:test_jar).should be_kind_of(ZipTask)
    end
  end
end

RSpec.shared_examples 'package_with_' do

  def prepare(options = {})
    packager = "package_with_#{@packaging}"
    write 'src/main/java/Source.java'
    write 'baz/src/main/java/Source.java'
    define 'foo', :version=>'1.0' do
      send packager, options
      define 'bar' ; define 'baz'
    end
  end

  def applied_to
    projects.select { |project| project.packages.first }.map(&:name)
  end

  it 'should create package of the right packaging with classifier' do
    prepare
    project('foo').packages.first.to_s.should =~ /foo-1.0-#{@packaging}.#{@ext}/
  end

  it 'should create package for projects that have source files' do
    prepare
    applied_to.should include('foo', 'foo:baz')
  end

  it 'should not create package for projects that have no source files' do
    prepare
    applied_to.should_not include('foo:bar')
  end

  it 'should limit to projects specified by :only' do
    prepare :only=>'baz'
    applied_to.should eql(['foo:baz'])
  end

  it 'should limit to projects specified by :only array' do
    prepare :only=>['baz']
    applied_to.should eql(['foo:baz'])
  end

  it 'should ignore project specified by :except' do
    prepare :except=>'baz'
    applied_to.should eql(['foo'])
  end

  it 'should ignore projects specified by :except array' do
    prepare :except=>['baz']
    applied_to.should eql(['foo'])
  end
end

describe 'package_with_sources' do
  it_should_behave_like 'package_with_'
  before { @packaging, @ext = :sources, 'jar' }
end

describe 'package_with_javadoc' do
  it_should_behave_like 'package_with_'
  before { @packaging, @ext = :javadoc, 'jar' }
end
